sigs.k8s.io/controller-runtime@v0.18.2/pkg/client/fake/client.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package fake
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"encoding/json"
    23  	"errors"
    24  	"fmt"
    25  	"reflect"
    26  	"runtime/debug"
    27  	"strconv"
    28  	"strings"
    29  	"sync"
    30  	"time"
    31  
    32  	// Using v4 to match upstream
    33  	jsonpatch "github.com/evanphx/json-patch"
    34  	corev1 "k8s.io/api/core/v1"
    35  	policyv1 "k8s.io/api/policy/v1"
    36  	policyv1beta1 "k8s.io/api/policy/v1beta1"
    37  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    38  	"k8s.io/apimachinery/pkg/api/meta"
    39  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    40  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    41  	"k8s.io/apimachinery/pkg/fields"
    42  	"k8s.io/apimachinery/pkg/labels"
    43  	"k8s.io/apimachinery/pkg/runtime"
    44  	"k8s.io/apimachinery/pkg/runtime/schema"
    45  	"k8s.io/apimachinery/pkg/types"
    46  	utilrand "k8s.io/apimachinery/pkg/util/rand"
    47  	"k8s.io/apimachinery/pkg/util/sets"
    48  	"k8s.io/apimachinery/pkg/util/strategicpatch"
    49  	"k8s.io/apimachinery/pkg/util/validation/field"
    50  	"k8s.io/apimachinery/pkg/watch"
    51  	"k8s.io/client-go/kubernetes/scheme"
    52  	"k8s.io/client-go/testing"
    53  
    54  	"sigs.k8s.io/controller-runtime/pkg/client"
    55  	"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
    56  	"sigs.k8s.io/controller-runtime/pkg/client/interceptor"
    57  	"sigs.k8s.io/controller-runtime/pkg/internal/field/selector"
    58  	"sigs.k8s.io/controller-runtime/pkg/internal/objectutil"
    59  )
    60  
    61  type versionedTracker struct {
    62  	testing.ObjectTracker
    63  	scheme                *runtime.Scheme
    64  	withStatusSubresource sets.Set[schema.GroupVersionKind]
    65  }
    66  
    67  type fakeClient struct {
    68  	tracker               versionedTracker
    69  	scheme                *runtime.Scheme
    70  	restMapper            meta.RESTMapper
    71  	withStatusSubresource sets.Set[schema.GroupVersionKind]
    72  
    73  	// indexes maps each GroupVersionKind (GVK) to the indexes registered for that GVK.
    74  	// The inner map maps from index name to IndexerFunc.
    75  	indexes map[schema.GroupVersionKind]map[string]client.IndexerFunc
    76  
    77  	schemeWriteLock sync.Mutex
    78  }
    79  
    80  var _ client.WithWatch = &fakeClient{}
    81  
    82  const (
    83  	maxNameLength          = 63
    84  	randomLength           = 5
    85  	maxGeneratedNameLength = maxNameLength - randomLength
    86  )
    87  
    88  // NewFakeClient creates a new fake client for testing.
    89  // You can choose to initialize it with a slice of runtime.Object.
    90  func NewFakeClient(initObjs ...runtime.Object) client.WithWatch {
    91  	return NewClientBuilder().WithRuntimeObjects(initObjs...).Build()
    92  }
    93  
    94  // NewClientBuilder returns a new builder to create a fake client.
    95  func NewClientBuilder() *ClientBuilder {
    96  	return &ClientBuilder{}
    97  }
    98  
    99  // ClientBuilder builds a fake client.
   100  type ClientBuilder struct {
   101  	scheme                *runtime.Scheme
   102  	restMapper            meta.RESTMapper
   103  	initObject            []client.Object
   104  	initLists             []client.ObjectList
   105  	initRuntimeObjects    []runtime.Object
   106  	withStatusSubresource []client.Object
   107  	objectTracker         testing.ObjectTracker
   108  	interceptorFuncs      *interceptor.Funcs
   109  
   110  	// indexes maps each GroupVersionKind (GVK) to the indexes registered for that GVK.
   111  	// The inner map maps from index name to IndexerFunc.
   112  	indexes map[schema.GroupVersionKind]map[string]client.IndexerFunc
   113  }
   114  
   115  // WithScheme sets this builder's internal scheme.
   116  // If not set, defaults to client-go's global scheme.Scheme.
   117  func (f *ClientBuilder) WithScheme(scheme *runtime.Scheme) *ClientBuilder {
   118  	f.scheme = scheme
   119  	return f
   120  }
   121  
   122  // WithRESTMapper sets this builder's restMapper.
   123  // The restMapper is directly set as mapper in the Client. This can be used for example
   124  // with a meta.DefaultRESTMapper to provide a static rest mapping.
   125  // If not set, defaults to an empty meta.DefaultRESTMapper.
   126  func (f *ClientBuilder) WithRESTMapper(restMapper meta.RESTMapper) *ClientBuilder {
   127  	f.restMapper = restMapper
   128  	return f
   129  }
   130  
   131  // WithObjects can be optionally used to initialize this fake client with client.Object(s).
   132  func (f *ClientBuilder) WithObjects(initObjs ...client.Object) *ClientBuilder {
   133  	f.initObject = append(f.initObject, initObjs...)
   134  	return f
   135  }
   136  
   137  // WithLists can be optionally used to initialize this fake client with client.ObjectList(s).
   138  func (f *ClientBuilder) WithLists(initLists ...client.ObjectList) *ClientBuilder {
   139  	f.initLists = append(f.initLists, initLists...)
   140  	return f
   141  }
   142  
   143  // WithRuntimeObjects can be optionally used to initialize this fake client with runtime.Object(s).
   144  func (f *ClientBuilder) WithRuntimeObjects(initRuntimeObjs ...runtime.Object) *ClientBuilder {
   145  	f.initRuntimeObjects = append(f.initRuntimeObjects, initRuntimeObjs...)
   146  	return f
   147  }
   148  
   149  // WithObjectTracker can be optionally used to initialize this fake client with testing.ObjectTracker.
   150  func (f *ClientBuilder) WithObjectTracker(ot testing.ObjectTracker) *ClientBuilder {
   151  	f.objectTracker = ot
   152  	return f
   153  }
   154  
   155  // WithIndex can be optionally used to register an index with name `field` and indexer `extractValue`
   156  // for API objects of the same GroupVersionKind (GVK) as `obj` in the fake client.
   157  // It can be invoked multiple times, both with objects of the same GVK or different ones.
   158  // Invoking WithIndex twice with the same `field` and GVK (via `obj`) arguments will panic.
   159  // WithIndex retrieves the GVK of `obj` using the scheme registered via WithScheme if
   160  // WithScheme was previously invoked, the default scheme otherwise.
   161  func (f *ClientBuilder) WithIndex(obj runtime.Object, field string, extractValue client.IndexerFunc) *ClientBuilder {
   162  	objScheme := f.scheme
   163  	if objScheme == nil {
   164  		objScheme = scheme.Scheme
   165  	}
   166  
   167  	gvk, err := apiutil.GVKForObject(obj, objScheme)
   168  	if err != nil {
   169  		panic(err)
   170  	}
   171  
   172  	// If this is the first index being registered, we initialize the map storing all the indexes.
   173  	if f.indexes == nil {
   174  		f.indexes = make(map[schema.GroupVersionKind]map[string]client.IndexerFunc)
   175  	}
   176  
   177  	// If this is the first index being registered for the GroupVersionKind of `obj`, we initialize
   178  	// the map storing the indexes for that GroupVersionKind.
   179  	if f.indexes[gvk] == nil {
   180  		f.indexes[gvk] = make(map[string]client.IndexerFunc)
   181  	}
   182  
   183  	if _, fieldAlreadyIndexed := f.indexes[gvk][field]; fieldAlreadyIndexed {
   184  		panic(fmt.Errorf("indexer conflict: field %s for GroupVersionKind %v is already indexed",
   185  			field, gvk))
   186  	}
   187  
   188  	f.indexes[gvk][field] = extractValue
   189  
   190  	return f
   191  }
   192  
   193  // WithStatusSubresource configures the passed object with a status subresource, which means
   194  // calls to Update and Patch will not alter its status.
   195  func (f *ClientBuilder) WithStatusSubresource(o ...client.Object) *ClientBuilder {
   196  	f.withStatusSubresource = append(f.withStatusSubresource, o...)
   197  	return f
   198  }
   199  
   200  // WithInterceptorFuncs configures the client methods to be intercepted using the provided interceptor.Funcs.
   201  func (f *ClientBuilder) WithInterceptorFuncs(interceptorFuncs interceptor.Funcs) *ClientBuilder {
   202  	f.interceptorFuncs = &interceptorFuncs
   203  	return f
   204  }
   205  
   206  // Build builds and returns a new fake client.
   207  func (f *ClientBuilder) Build() client.WithWatch {
   208  	if f.scheme == nil {
   209  		f.scheme = scheme.Scheme
   210  	}
   211  	if f.restMapper == nil {
   212  		f.restMapper = meta.NewDefaultRESTMapper([]schema.GroupVersion{})
   213  	}
   214  
   215  	var tracker versionedTracker
   216  
   217  	withStatusSubResource := sets.New(inTreeResourcesWithStatus()...)
   218  	for _, o := range f.withStatusSubresource {
   219  		gvk, err := apiutil.GVKForObject(o, f.scheme)
   220  		if err != nil {
   221  			panic(fmt.Errorf("failed to get gvk for object %T: %w", withStatusSubResource, err))
   222  		}
   223  		withStatusSubResource.Insert(gvk)
   224  	}
   225  
   226  	if f.objectTracker == nil {
   227  		tracker = versionedTracker{ObjectTracker: testing.NewObjectTracker(f.scheme, scheme.Codecs.UniversalDecoder()), scheme: f.scheme, withStatusSubresource: withStatusSubResource}
   228  	} else {
   229  		tracker = versionedTracker{ObjectTracker: f.objectTracker, scheme: f.scheme, withStatusSubresource: withStatusSubResource}
   230  	}
   231  
   232  	for _, obj := range f.initObject {
   233  		if err := tracker.Add(obj); err != nil {
   234  			panic(fmt.Errorf("failed to add object %v to fake client: %w", obj, err))
   235  		}
   236  	}
   237  	for _, obj := range f.initLists {
   238  		if err := tracker.Add(obj); err != nil {
   239  			panic(fmt.Errorf("failed to add list %v to fake client: %w", obj, err))
   240  		}
   241  	}
   242  	for _, obj := range f.initRuntimeObjects {
   243  		if err := tracker.Add(obj); err != nil {
   244  			panic(fmt.Errorf("failed to add runtime object %v to fake client: %w", obj, err))
   245  		}
   246  	}
   247  
   248  	var result client.WithWatch = &fakeClient{
   249  		tracker:               tracker,
   250  		scheme:                f.scheme,
   251  		restMapper:            f.restMapper,
   252  		indexes:               f.indexes,
   253  		withStatusSubresource: withStatusSubResource,
   254  	}
   255  
   256  	if f.interceptorFuncs != nil {
   257  		result = interceptor.NewClient(result, *f.interceptorFuncs)
   258  	}
   259  
   260  	return result
   261  }
   262  
   263  const trackerAddResourceVersion = "999"
   264  
   265  func (t versionedTracker) Add(obj runtime.Object) error {
   266  	var objects []runtime.Object
   267  	if meta.IsListType(obj) {
   268  		var err error
   269  		objects, err = meta.ExtractList(obj)
   270  		if err != nil {
   271  			return err
   272  		}
   273  	} else {
   274  		objects = []runtime.Object{obj}
   275  	}
   276  	for _, obj := range objects {
   277  		accessor, err := meta.Accessor(obj)
   278  		if err != nil {
   279  			return fmt.Errorf("failed to get accessor for object: %w", err)
   280  		}
   281  		if accessor.GetDeletionTimestamp() != nil && len(accessor.GetFinalizers()) == 0 {
   282  			return fmt.Errorf("refusing to create obj %s with metadata.deletionTimestamp but no finalizers", accessor.GetName())
   283  		}
   284  		if accessor.GetResourceVersion() == "" {
   285  			// We use a "magic" value of 999 here because this field
   286  			// is parsed as uint and and 0 is already used in Update.
   287  			// As we can't go lower, go very high instead so this can
   288  			// be recognized
   289  			accessor.SetResourceVersion(trackerAddResourceVersion)
   290  		}
   291  
   292  		obj, err = convertFromUnstructuredIfNecessary(t.scheme, obj)
   293  		if err != nil {
   294  			return err
   295  		}
   296  		if err := t.ObjectTracker.Add(obj); err != nil {
   297  			return err
   298  		}
   299  	}
   300  
   301  	return nil
   302  }
   303  
   304  func (t versionedTracker) Create(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error {
   305  	accessor, err := meta.Accessor(obj)
   306  	if err != nil {
   307  		return fmt.Errorf("failed to get accessor for object: %w", err)
   308  	}
   309  	if accessor.GetName() == "" {
   310  		return apierrors.NewInvalid(
   311  			obj.GetObjectKind().GroupVersionKind().GroupKind(),
   312  			accessor.GetName(),
   313  			field.ErrorList{field.Required(field.NewPath("metadata.name"), "name is required")})
   314  	}
   315  	if accessor.GetResourceVersion() != "" {
   316  		return apierrors.NewBadRequest("resourceVersion can not be set for Create requests")
   317  	}
   318  	accessor.SetResourceVersion("1")
   319  	obj, err = convertFromUnstructuredIfNecessary(t.scheme, obj)
   320  	if err != nil {
   321  		return err
   322  	}
   323  	if err := t.ObjectTracker.Create(gvr, obj, ns); err != nil {
   324  		accessor.SetResourceVersion("")
   325  		return err
   326  	}
   327  
   328  	return nil
   329  }
   330  
   331  // convertFromUnstructuredIfNecessary will convert runtime.Unstructured for a GVK that is recognized
   332  // by the schema into the whatever the schema produces with New() for said GVK.
   333  // This is required because the tracker unconditionally saves on manipulations, but its List() implementation
   334  // tries to assign whatever it finds into a ListType it gets from schema.New() - Thus we have to ensure
   335  // we save as the very same type, otherwise subsequent List requests will fail.
   336  func convertFromUnstructuredIfNecessary(s *runtime.Scheme, o runtime.Object) (runtime.Object, error) {
   337  	u, isUnstructured := o.(runtime.Unstructured)
   338  	if !isUnstructured {
   339  		return o, nil
   340  	}
   341  	gvk := o.GetObjectKind().GroupVersionKind()
   342  	if !s.Recognizes(gvk) {
   343  		return o, nil
   344  	}
   345  
   346  	typed, err := s.New(gvk)
   347  	if err != nil {
   348  		return nil, fmt.Errorf("scheme recognizes %s but failed to produce an object for it: %w", gvk, err)
   349  	}
   350  
   351  	unstructuredSerialized, err := json.Marshal(u)
   352  	if err != nil {
   353  		return nil, fmt.Errorf("failed to serialize %T: %w", unstructuredSerialized, err)
   354  	}
   355  	if err := json.Unmarshal(unstructuredSerialized, typed); err != nil {
   356  		return nil, fmt.Errorf("failed to unmarshal the content of %T into %T: %w", u, typed, err)
   357  	}
   358  
   359  	return typed, nil
   360  }
   361  
   362  func (t versionedTracker) Update(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error {
   363  	isStatus := false
   364  	// We apply patches using a client-go reaction that ends up calling the trackers Update. As we can't change
   365  	// that reaction, we use the callstack to figure out if this originated from the status client.
   366  	if bytes.Contains(debug.Stack(), []byte("sigs.k8s.io/controller-runtime/pkg/client/fake.(*fakeSubResourceClient).statusPatch")) {
   367  		isStatus = true
   368  	}
   369  	return t.update(gvr, obj, ns, isStatus, false)
   370  }
   371  
   372  func (t versionedTracker) update(gvr schema.GroupVersionResource, obj runtime.Object, ns string, isStatus bool, deleting bool) error {
   373  	accessor, err := meta.Accessor(obj)
   374  	if err != nil {
   375  		return fmt.Errorf("failed to get accessor for object: %w", err)
   376  	}
   377  
   378  	if accessor.GetName() == "" {
   379  		return apierrors.NewInvalid(
   380  			obj.GetObjectKind().GroupVersionKind().GroupKind(),
   381  			accessor.GetName(),
   382  			field.ErrorList{field.Required(field.NewPath("metadata.name"), "name is required")})
   383  	}
   384  
   385  	gvk, err := apiutil.GVKForObject(obj, t.scheme)
   386  	if err != nil {
   387  		return err
   388  	}
   389  
   390  	oldObject, err := t.ObjectTracker.Get(gvr, ns, accessor.GetName())
   391  	if err != nil {
   392  		// If the resource is not found and the resource allows create on update, issue a
   393  		// create instead.
   394  		if apierrors.IsNotFound(err) && allowsCreateOnUpdate(gvk) {
   395  			return t.Create(gvr, obj, ns)
   396  		}
   397  		return err
   398  	}
   399  
   400  	if t.withStatusSubresource.Has(gvk) {
   401  		if isStatus { // copy everything but status and metadata.ResourceVersion from original object
   402  			if err := copyStatusFrom(obj, oldObject); err != nil {
   403  				return fmt.Errorf("failed to copy non-status field for object with status subresouce: %w", err)
   404  			}
   405  			passedRV := accessor.GetResourceVersion()
   406  			if err := copyFrom(oldObject, obj); err != nil {
   407  				return fmt.Errorf("failed to restore non-status fields: %w", err)
   408  			}
   409  			accessor.SetResourceVersion(passedRV)
   410  		} else { // copy status from original object
   411  			if err := copyStatusFrom(oldObject, obj); err != nil {
   412  				return fmt.Errorf("failed to copy the status for object with status subresource: %w", err)
   413  			}
   414  		}
   415  	} else if isStatus {
   416  		return apierrors.NewNotFound(gvr.GroupResource(), accessor.GetName())
   417  	}
   418  
   419  	oldAccessor, err := meta.Accessor(oldObject)
   420  	if err != nil {
   421  		return err
   422  	}
   423  
   424  	// If the new object does not have the resource version set and it allows unconditional update,
   425  	// default it to the resource version of the existing resource
   426  	if accessor.GetResourceVersion() == "" {
   427  		switch {
   428  		case allowsUnconditionalUpdate(gvk):
   429  			accessor.SetResourceVersion(oldAccessor.GetResourceVersion())
   430  		case bytes.
   431  			Contains(debug.Stack(), []byte("sigs.k8s.io/controller-runtime/pkg/client/fake.(*fakeClient).Patch")):
   432  			// We apply patches using a client-go reaction that ends up calling the trackers Update. As we can't change
   433  			// that reaction, we use the callstack to figure out if this originated from the "fakeClient.Patch" func.
   434  			accessor.SetResourceVersion(oldAccessor.GetResourceVersion())
   435  		}
   436  	}
   437  
   438  	if accessor.GetResourceVersion() != oldAccessor.GetResourceVersion() {
   439  		return apierrors.NewConflict(gvr.GroupResource(), accessor.GetName(), errors.New("object was modified"))
   440  	}
   441  	if oldAccessor.GetResourceVersion() == "" {
   442  		oldAccessor.SetResourceVersion("0")
   443  	}
   444  	intResourceVersion, err := strconv.ParseUint(oldAccessor.GetResourceVersion(), 10, 64)
   445  	if err != nil {
   446  		return fmt.Errorf("can not convert resourceVersion %q to int: %w", oldAccessor.GetResourceVersion(), err)
   447  	}
   448  	intResourceVersion++
   449  	accessor.SetResourceVersion(strconv.FormatUint(intResourceVersion, 10))
   450  
   451  	if !deleting && !deletionTimestampEqual(accessor, oldAccessor) {
   452  		return fmt.Errorf("error: Unable to edit %s: metadata.deletionTimestamp field is immutable", accessor.GetName())
   453  	}
   454  
   455  	if !accessor.GetDeletionTimestamp().IsZero() && len(accessor.GetFinalizers()) == 0 {
   456  		return t.ObjectTracker.Delete(gvr, accessor.GetNamespace(), accessor.GetName())
   457  	}
   458  	obj, err = convertFromUnstructuredIfNecessary(t.scheme, obj)
   459  	if err != nil {
   460  		return err
   461  	}
   462  	return t.ObjectTracker.Update(gvr, obj, ns)
   463  }
   464  
   465  func (c *fakeClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
   466  	gvr, err := getGVRFromObject(obj, c.scheme)
   467  	if err != nil {
   468  		return err
   469  	}
   470  	o, err := c.tracker.Get(gvr, key.Namespace, key.Name)
   471  	if err != nil {
   472  		return err
   473  	}
   474  
   475  	if _, isUnstructured := obj.(runtime.Unstructured); isUnstructured {
   476  		gvk, err := apiutil.GVKForObject(obj, c.scheme)
   477  		if err != nil {
   478  			return err
   479  		}
   480  		ta, err := meta.TypeAccessor(o)
   481  		if err != nil {
   482  			return err
   483  		}
   484  		ta.SetKind(gvk.Kind)
   485  		ta.SetAPIVersion(gvk.GroupVersion().String())
   486  	}
   487  
   488  	j, err := json.Marshal(o)
   489  	if err != nil {
   490  		return err
   491  	}
   492  	zero(obj)
   493  	return json.Unmarshal(j, obj)
   494  }
   495  
   496  func (c *fakeClient) Watch(ctx context.Context, list client.ObjectList, opts ...client.ListOption) (watch.Interface, error) {
   497  	gvk, err := apiutil.GVKForObject(list, c.scheme)
   498  	if err != nil {
   499  		return nil, err
   500  	}
   501  
   502  	gvk.Kind = strings.TrimSuffix(gvk.Kind, "List")
   503  
   504  	listOpts := client.ListOptions{}
   505  	listOpts.ApplyOptions(opts)
   506  
   507  	gvr, _ := meta.UnsafeGuessKindToResource(gvk)
   508  	return c.tracker.Watch(gvr, listOpts.Namespace)
   509  }
   510  
   511  func (c *fakeClient) List(ctx context.Context, obj client.ObjectList, opts ...client.ListOption) error {
   512  	gvk, err := apiutil.GVKForObject(obj, c.scheme)
   513  	if err != nil {
   514  		return err
   515  	}
   516  
   517  	originalKind := gvk.Kind
   518  
   519  	gvk.Kind = strings.TrimSuffix(gvk.Kind, "List")
   520  
   521  	if _, isUnstructuredList := obj.(runtime.Unstructured); isUnstructuredList && !c.scheme.Recognizes(gvk) {
   522  		// We need to register the ListKind with UnstructuredList:
   523  		// https://github.com/kubernetes/kubernetes/blob/7b2776b89fb1be28d4e9203bdeec079be903c103/staging/src/k8s.io/client-go/dynamic/fake/simple.go#L44-L51
   524  		c.schemeWriteLock.Lock()
   525  		c.scheme.AddKnownTypeWithName(gvk.GroupVersion().WithKind(gvk.Kind+"List"), &unstructured.UnstructuredList{})
   526  		c.schemeWriteLock.Unlock()
   527  	}
   528  
   529  	listOpts := client.ListOptions{}
   530  	listOpts.ApplyOptions(opts)
   531  
   532  	gvr, _ := meta.UnsafeGuessKindToResource(gvk)
   533  	o, err := c.tracker.List(gvr, gvk, listOpts.Namespace)
   534  	if err != nil {
   535  		return err
   536  	}
   537  
   538  	if _, isUnstructured := obj.(runtime.Unstructured); isUnstructured {
   539  		ta, err := meta.TypeAccessor(o)
   540  		if err != nil {
   541  			return err
   542  		}
   543  		ta.SetKind(originalKind)
   544  		ta.SetAPIVersion(gvk.GroupVersion().String())
   545  	}
   546  
   547  	j, err := json.Marshal(o)
   548  	if err != nil {
   549  		return err
   550  	}
   551  	zero(obj)
   552  	if err := json.Unmarshal(j, obj); err != nil {
   553  		return err
   554  	}
   555  
   556  	if listOpts.LabelSelector == nil && listOpts.FieldSelector == nil {
   557  		return nil
   558  	}
   559  
   560  	// If we're here, either a label or field selector are specified (or both), so before we return
   561  	// the list we must filter it. If both selectors are set, they are ANDed.
   562  	objs, err := meta.ExtractList(obj)
   563  	if err != nil {
   564  		return err
   565  	}
   566  
   567  	filteredList, err := c.filterList(objs, gvk, listOpts.LabelSelector, listOpts.FieldSelector)
   568  	if err != nil {
   569  		return err
   570  	}
   571  
   572  	return meta.SetList(obj, filteredList)
   573  }
   574  
   575  func (c *fakeClient) filterList(list []runtime.Object, gvk schema.GroupVersionKind, ls labels.Selector, fs fields.Selector) ([]runtime.Object, error) {
   576  	// Filter the objects with the label selector
   577  	filteredList := list
   578  	if ls != nil {
   579  		objsFilteredByLabel, err := objectutil.FilterWithLabels(list, ls)
   580  		if err != nil {
   581  			return nil, err
   582  		}
   583  		filteredList = objsFilteredByLabel
   584  	}
   585  
   586  	// Filter the result of the previous pass with the field selector
   587  	if fs != nil {
   588  		objsFilteredByField, err := c.filterWithFields(filteredList, gvk, fs)
   589  		if err != nil {
   590  			return nil, err
   591  		}
   592  		filteredList = objsFilteredByField
   593  	}
   594  
   595  	return filteredList, nil
   596  }
   597  
   598  func (c *fakeClient) filterWithFields(list []runtime.Object, gvk schema.GroupVersionKind, fs fields.Selector) ([]runtime.Object, error) {
   599  	requiresExact := selector.RequiresExactMatch(fs)
   600  	if !requiresExact {
   601  		return nil, fmt.Errorf("field selector %s is not in one of the two supported forms \"key==val\" or \"key=val\"",
   602  			fs)
   603  	}
   604  
   605  	// Field selection is mimicked via indexes, so there's no sane answer this function can give
   606  	// if there are no indexes registered for the GroupVersionKind of the objects in the list.
   607  	indexes := c.indexes[gvk]
   608  	for _, req := range fs.Requirements() {
   609  		if len(indexes) == 0 || indexes[req.Field] == nil {
   610  			return nil, fmt.Errorf("List on GroupVersionKind %v specifies selector on field %s, but no "+
   611  				"index with name %s has been registered for GroupVersionKind %v", gvk, req.Field, req.Field, gvk)
   612  		}
   613  	}
   614  
   615  	filteredList := make([]runtime.Object, 0, len(list))
   616  	for _, obj := range list {
   617  		matches := true
   618  		for _, req := range fs.Requirements() {
   619  			indexExtractor := indexes[req.Field]
   620  			if !c.objMatchesFieldSelector(obj, indexExtractor, req.Value) {
   621  				matches = false
   622  				break
   623  			}
   624  		}
   625  		if matches {
   626  			filteredList = append(filteredList, obj)
   627  		}
   628  	}
   629  	return filteredList, nil
   630  }
   631  
   632  func (c *fakeClient) objMatchesFieldSelector(o runtime.Object, extractIndex client.IndexerFunc, val string) bool {
   633  	obj, isClientObject := o.(client.Object)
   634  	if !isClientObject {
   635  		panic(fmt.Errorf("expected object %v to be of type client.Object, but it's not", o))
   636  	}
   637  
   638  	for _, extractedVal := range extractIndex(obj) {
   639  		if extractedVal == val {
   640  			return true
   641  		}
   642  	}
   643  
   644  	return false
   645  }
   646  
   647  func (c *fakeClient) Scheme() *runtime.Scheme {
   648  	return c.scheme
   649  }
   650  
   651  func (c *fakeClient) RESTMapper() meta.RESTMapper {
   652  	return c.restMapper
   653  }
   654  
   655  // GroupVersionKindFor returns the GroupVersionKind for the given object.
   656  func (c *fakeClient) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) {
   657  	return apiutil.GVKForObject(obj, c.scheme)
   658  }
   659  
   660  // IsObjectNamespaced returns true if the GroupVersionKind of the object is namespaced.
   661  func (c *fakeClient) IsObjectNamespaced(obj runtime.Object) (bool, error) {
   662  	return apiutil.IsObjectNamespaced(obj, c.scheme, c.restMapper)
   663  }
   664  
   665  func (c *fakeClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
   666  	createOptions := &client.CreateOptions{}
   667  	createOptions.ApplyOptions(opts)
   668  
   669  	for _, dryRunOpt := range createOptions.DryRun {
   670  		if dryRunOpt == metav1.DryRunAll {
   671  			return nil
   672  		}
   673  	}
   674  
   675  	gvr, err := getGVRFromObject(obj, c.scheme)
   676  	if err != nil {
   677  		return err
   678  	}
   679  	accessor, err := meta.Accessor(obj)
   680  	if err != nil {
   681  		return err
   682  	}
   683  
   684  	if accessor.GetName() == "" && accessor.GetGenerateName() != "" {
   685  		base := accessor.GetGenerateName()
   686  		if len(base) > maxGeneratedNameLength {
   687  			base = base[:maxGeneratedNameLength]
   688  		}
   689  		accessor.SetName(fmt.Sprintf("%s%s", base, utilrand.String(randomLength)))
   690  	}
   691  	// Ignore attempts to set deletion timestamp
   692  	if !accessor.GetDeletionTimestamp().IsZero() {
   693  		accessor.SetDeletionTimestamp(nil)
   694  	}
   695  
   696  	return c.tracker.Create(gvr, obj, accessor.GetNamespace())
   697  }
   698  
   699  func (c *fakeClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
   700  	gvr, err := getGVRFromObject(obj, c.scheme)
   701  	if err != nil {
   702  		return err
   703  	}
   704  	accessor, err := meta.Accessor(obj)
   705  	if err != nil {
   706  		return err
   707  	}
   708  	delOptions := client.DeleteOptions{}
   709  	delOptions.ApplyOptions(opts)
   710  
   711  	for _, dryRunOpt := range delOptions.DryRun {
   712  		if dryRunOpt == metav1.DryRunAll {
   713  			return nil
   714  		}
   715  	}
   716  
   717  	// Check the ResourceVersion if that Precondition was specified.
   718  	if delOptions.Preconditions != nil && delOptions.Preconditions.ResourceVersion != nil {
   719  		name := accessor.GetName()
   720  		dbObj, err := c.tracker.Get(gvr, accessor.GetNamespace(), name)
   721  		if err != nil {
   722  			return err
   723  		}
   724  		oldAccessor, err := meta.Accessor(dbObj)
   725  		if err != nil {
   726  			return err
   727  		}
   728  		actualRV := oldAccessor.GetResourceVersion()
   729  		expectRV := *delOptions.Preconditions.ResourceVersion
   730  		if actualRV != expectRV {
   731  			msg := fmt.Sprintf(
   732  				"the ResourceVersion in the precondition (%s) does not match the ResourceVersion in record (%s). "+
   733  					"The object might have been modified",
   734  				expectRV, actualRV)
   735  			return apierrors.NewConflict(gvr.GroupResource(), name, errors.New(msg))
   736  		}
   737  	}
   738  
   739  	return c.deleteObject(gvr, accessor)
   740  }
   741  
   742  func (c *fakeClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error {
   743  	gvk, err := apiutil.GVKForObject(obj, c.scheme)
   744  	if err != nil {
   745  		return err
   746  	}
   747  
   748  	dcOptions := client.DeleteAllOfOptions{}
   749  	dcOptions.ApplyOptions(opts)
   750  
   751  	for _, dryRunOpt := range dcOptions.DryRun {
   752  		if dryRunOpt == metav1.DryRunAll {
   753  			return nil
   754  		}
   755  	}
   756  
   757  	gvr, _ := meta.UnsafeGuessKindToResource(gvk)
   758  	o, err := c.tracker.List(gvr, gvk, dcOptions.Namespace)
   759  	if err != nil {
   760  		return err
   761  	}
   762  
   763  	objs, err := meta.ExtractList(o)
   764  	if err != nil {
   765  		return err
   766  	}
   767  	filteredObjs, err := objectutil.FilterWithLabels(objs, dcOptions.LabelSelector)
   768  	if err != nil {
   769  		return err
   770  	}
   771  	for _, o := range filteredObjs {
   772  		accessor, err := meta.Accessor(o)
   773  		if err != nil {
   774  			return err
   775  		}
   776  		err = c.deleteObject(gvr, accessor)
   777  		if err != nil {
   778  			return err
   779  		}
   780  	}
   781  	return nil
   782  }
   783  
   784  func (c *fakeClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
   785  	return c.update(obj, false, opts...)
   786  }
   787  
   788  func (c *fakeClient) update(obj client.Object, isStatus bool, opts ...client.UpdateOption) error {
   789  	updateOptions := &client.UpdateOptions{}
   790  	updateOptions.ApplyOptions(opts)
   791  
   792  	for _, dryRunOpt := range updateOptions.DryRun {
   793  		if dryRunOpt == metav1.DryRunAll {
   794  			return nil
   795  		}
   796  	}
   797  
   798  	gvr, err := getGVRFromObject(obj, c.scheme)
   799  	if err != nil {
   800  		return err
   801  	}
   802  	accessor, err := meta.Accessor(obj)
   803  	if err != nil {
   804  		return err
   805  	}
   806  	return c.tracker.update(gvr, obj, accessor.GetNamespace(), isStatus, false)
   807  }
   808  
   809  func (c *fakeClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
   810  	return c.patch(obj, patch, opts...)
   811  }
   812  
   813  func (c *fakeClient) patch(obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
   814  	patchOptions := &client.PatchOptions{}
   815  	patchOptions.ApplyOptions(opts)
   816  
   817  	for _, dryRunOpt := range patchOptions.DryRun {
   818  		if dryRunOpt == metav1.DryRunAll {
   819  			return nil
   820  		}
   821  	}
   822  
   823  	gvr, err := getGVRFromObject(obj, c.scheme)
   824  	if err != nil {
   825  		return err
   826  	}
   827  	accessor, err := meta.Accessor(obj)
   828  	if err != nil {
   829  		return err
   830  	}
   831  	data, err := patch.Data(obj)
   832  	if err != nil {
   833  		return err
   834  	}
   835  
   836  	gvk, err := apiutil.GVKForObject(obj, c.scheme)
   837  	if err != nil {
   838  		return err
   839  	}
   840  
   841  	oldObj, err := c.tracker.Get(gvr, accessor.GetNamespace(), accessor.GetName())
   842  	if err != nil {
   843  		return err
   844  	}
   845  	oldAccessor, err := meta.Accessor(oldObj)
   846  	if err != nil {
   847  		return err
   848  	}
   849  
   850  	// Apply patch without updating object.
   851  	// To remain in accordance with the behavior of k8s api behavior,
   852  	// a patch must not allow for changes to the deletionTimestamp of an object.
   853  	// The reaction() function applies the patch to the object and calls Update(),
   854  	// whereas dryPatch() replicates this behavior but skips the call to Update().
   855  	// This ensures that the patch may be rejected if a deletionTimestamp is modified, prior
   856  	// to updating the object.
   857  	action := testing.NewPatchAction(gvr, accessor.GetNamespace(), accessor.GetName(), patch.Type(), data)
   858  	o, err := dryPatch(action, c.tracker)
   859  	if err != nil {
   860  		return err
   861  	}
   862  	newObj, err := meta.Accessor(o)
   863  	if err != nil {
   864  		return err
   865  	}
   866  
   867  	// Validate that deletionTimestamp has not been changed
   868  	if !deletionTimestampEqual(newObj, oldAccessor) {
   869  		return fmt.Errorf("rejected patch, metadata.deletionTimestamp immutable")
   870  	}
   871  
   872  	reaction := testing.ObjectReaction(c.tracker)
   873  	handled, o, err := reaction(action)
   874  	if err != nil {
   875  		return err
   876  	}
   877  	if !handled {
   878  		panic("tracker could not handle patch method")
   879  	}
   880  
   881  	if _, isUnstructured := obj.(runtime.Unstructured); isUnstructured {
   882  		ta, err := meta.TypeAccessor(o)
   883  		if err != nil {
   884  			return err
   885  		}
   886  		ta.SetKind(gvk.Kind)
   887  		ta.SetAPIVersion(gvk.GroupVersion().String())
   888  	}
   889  
   890  	j, err := json.Marshal(o)
   891  	if err != nil {
   892  		return err
   893  	}
   894  	zero(obj)
   895  	return json.Unmarshal(j, obj)
   896  }
   897  
   898  // Applying a patch results in a deletionTimestamp that is truncated to the nearest second.
   899  // Check that the diff between a new and old deletion timestamp is within a reasonable threshold
   900  // to be considered unchanged.
   901  func deletionTimestampEqual(newObj metav1.Object, obj metav1.Object) bool {
   902  	newTime := newObj.GetDeletionTimestamp()
   903  	oldTime := obj.GetDeletionTimestamp()
   904  
   905  	if newTime == nil || oldTime == nil {
   906  		return newTime == oldTime
   907  	}
   908  	return newTime.Time.Sub(oldTime.Time).Abs() < time.Second
   909  }
   910  
   911  // The behavior of applying the patch is pulled out into dryPatch(),
   912  // which applies the patch and returns an object, but does not Update() the object.
   913  // This function returns a patched runtime object that may then be validated before a call to Update() is executed.
   914  // This results in some code duplication, but was found to be a cleaner alternative than unmarshalling and introspecting the patch data
   915  // and easier than refactoring the k8s client-go method upstream.
   916  // Duplicate of upstream: https://github.com/kubernetes/client-go/blob/783d0d33626e59d55d52bfd7696b775851f92107/testing/fixture.go#L146-L194
   917  func dryPatch(action testing.PatchActionImpl, tracker testing.ObjectTracker) (runtime.Object, error) {
   918  	ns := action.GetNamespace()
   919  	gvr := action.GetResource()
   920  
   921  	obj, err := tracker.Get(gvr, ns, action.GetName())
   922  	if err != nil {
   923  		return nil, err
   924  	}
   925  
   926  	old, err := json.Marshal(obj)
   927  	if err != nil {
   928  		return nil, err
   929  	}
   930  
   931  	// reset the object in preparation to unmarshal, since unmarshal does not guarantee that fields
   932  	// in obj that are removed by patch are cleared
   933  	value := reflect.ValueOf(obj)
   934  	value.Elem().Set(reflect.New(value.Type().Elem()).Elem())
   935  
   936  	switch action.GetPatchType() {
   937  	case types.JSONPatchType:
   938  		patch, err := jsonpatch.DecodePatch(action.GetPatch())
   939  		if err != nil {
   940  			return nil, err
   941  		}
   942  		modified, err := patch.Apply(old)
   943  		if err != nil {
   944  			return nil, err
   945  		}
   946  
   947  		if err = json.Unmarshal(modified, obj); err != nil {
   948  			return nil, err
   949  		}
   950  	case types.MergePatchType:
   951  		modified, err := jsonpatch.MergePatch(old, action.GetPatch())
   952  		if err != nil {
   953  			return nil, err
   954  		}
   955  
   956  		if err := json.Unmarshal(modified, obj); err != nil {
   957  			return nil, err
   958  		}
   959  	case types.StrategicMergePatchType:
   960  		mergedByte, err := strategicpatch.StrategicMergePatch(old, action.GetPatch(), obj)
   961  		if err != nil {
   962  			return nil, err
   963  		}
   964  		if err = json.Unmarshal(mergedByte, obj); err != nil {
   965  			return nil, err
   966  		}
   967  	case types.ApplyPatchType:
   968  		return nil, errors.New("apply patches are not supported in the fake client. Follow https://github.com/kubernetes/kubernetes/issues/115598 for the current status")
   969  	default:
   970  		return nil, fmt.Errorf("%s PatchType is not supported", action.GetPatchType())
   971  	}
   972  	return obj, nil
   973  }
   974  
   975  // copyStatusFrom copies the status from old into new
   976  func copyStatusFrom(old, new runtime.Object) error {
   977  	oldMapStringAny, err := toMapStringAny(old)
   978  	if err != nil {
   979  		return fmt.Errorf("failed to convert old to *unstructured.Unstructured: %w", err)
   980  	}
   981  	newMapStringAny, err := toMapStringAny(new)
   982  	if err != nil {
   983  		return fmt.Errorf("failed to convert new to *unststructured.Unstructured: %w", err)
   984  	}
   985  
   986  	newMapStringAny["status"] = oldMapStringAny["status"]
   987  
   988  	if err := fromMapStringAny(newMapStringAny, new); err != nil {
   989  		return fmt.Errorf("failed to convert back from map[string]any: %w", err)
   990  	}
   991  
   992  	return nil
   993  }
   994  
   995  // copyFrom copies from old into new
   996  func copyFrom(old, new runtime.Object) error {
   997  	oldMapStringAny, err := toMapStringAny(old)
   998  	if err != nil {
   999  		return fmt.Errorf("failed to convert old to *unstructured.Unstructured: %w", err)
  1000  	}
  1001  	if err := fromMapStringAny(oldMapStringAny, new); err != nil {
  1002  		return fmt.Errorf("failed to convert back from map[string]any: %w", err)
  1003  	}
  1004  
  1005  	return nil
  1006  }
  1007  
  1008  func toMapStringAny(obj runtime.Object) (map[string]any, error) {
  1009  	if unstructured, isUnstructured := obj.(*unstructured.Unstructured); isUnstructured {
  1010  		return unstructured.Object, nil
  1011  	}
  1012  
  1013  	serialized, err := json.Marshal(obj)
  1014  	if err != nil {
  1015  		return nil, err
  1016  	}
  1017  
  1018  	u := map[string]any{}
  1019  	return u, json.Unmarshal(serialized, &u)
  1020  }
  1021  
  1022  func fromMapStringAny(u map[string]any, target runtime.Object) error {
  1023  	if targetUnstructured, isUnstructured := target.(*unstructured.Unstructured); isUnstructured {
  1024  		targetUnstructured.Object = u
  1025  		return nil
  1026  	}
  1027  
  1028  	serialized, err := json.Marshal(u)
  1029  	if err != nil {
  1030  		return fmt.Errorf("failed to serialize: %w", err)
  1031  	}
  1032  
  1033  	zero(target)
  1034  	if err := json.Unmarshal(serialized, &target); err != nil {
  1035  		return fmt.Errorf("failed to deserialize: %w", err)
  1036  	}
  1037  
  1038  	return nil
  1039  }
  1040  
  1041  func (c *fakeClient) Status() client.SubResourceWriter {
  1042  	return c.SubResource("status")
  1043  }
  1044  
  1045  func (c *fakeClient) SubResource(subResource string) client.SubResourceClient {
  1046  	return &fakeSubResourceClient{client: c, subResource: subResource}
  1047  }
  1048  
  1049  func (c *fakeClient) deleteObject(gvr schema.GroupVersionResource, accessor metav1.Object) error {
  1050  	old, err := c.tracker.Get(gvr, accessor.GetNamespace(), accessor.GetName())
  1051  	if err == nil {
  1052  		oldAccessor, err := meta.Accessor(old)
  1053  		if err == nil {
  1054  			if len(oldAccessor.GetFinalizers()) > 0 {
  1055  				now := metav1.Now()
  1056  				oldAccessor.SetDeletionTimestamp(&now)
  1057  				// Call update directly with mutability parameter set to true to allow
  1058  				// changes to deletionTimestamp
  1059  				return c.tracker.update(gvr, old, accessor.GetNamespace(), false, true)
  1060  			}
  1061  		}
  1062  	}
  1063  
  1064  	//TODO: implement propagation
  1065  	return c.tracker.Delete(gvr, accessor.GetNamespace(), accessor.GetName())
  1066  }
  1067  
  1068  func getGVRFromObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersionResource, error) {
  1069  	gvk, err := apiutil.GVKForObject(obj, scheme)
  1070  	if err != nil {
  1071  		return schema.GroupVersionResource{}, err
  1072  	}
  1073  	gvr, _ := meta.UnsafeGuessKindToResource(gvk)
  1074  	return gvr, nil
  1075  }
  1076  
  1077  type fakeSubResourceClient struct {
  1078  	client      *fakeClient
  1079  	subResource string
  1080  }
  1081  
  1082  func (sw *fakeSubResourceClient) Get(ctx context.Context, obj, subResource client.Object, opts ...client.SubResourceGetOption) error {
  1083  	panic("fakeSubResourceClient does not support get")
  1084  }
  1085  
  1086  func (sw *fakeSubResourceClient) Create(ctx context.Context, obj client.Object, subResource client.Object, opts ...client.SubResourceCreateOption) error {
  1087  	switch sw.subResource {
  1088  	case "eviction":
  1089  		_, isEviction := subResource.(*policyv1beta1.Eviction)
  1090  		if !isEviction {
  1091  			_, isEviction = subResource.(*policyv1.Eviction)
  1092  		}
  1093  		if !isEviction {
  1094  			return apierrors.NewBadRequest(fmt.Sprintf("got invalid type %t, expected Eviction", subResource))
  1095  		}
  1096  		if _, isPod := obj.(*corev1.Pod); !isPod {
  1097  			return apierrors.NewNotFound(schema.GroupResource{}, "")
  1098  		}
  1099  
  1100  		return sw.client.Delete(ctx, obj)
  1101  	default:
  1102  		return fmt.Errorf("fakeSubResourceWriter does not support create for %s", sw.subResource)
  1103  	}
  1104  }
  1105  
  1106  func (sw *fakeSubResourceClient) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
  1107  	updateOptions := client.SubResourceUpdateOptions{}
  1108  	updateOptions.ApplyOptions(opts)
  1109  
  1110  	body := obj
  1111  	if updateOptions.SubResourceBody != nil {
  1112  		body = updateOptions.SubResourceBody
  1113  	}
  1114  	return sw.client.update(body, true, &updateOptions.UpdateOptions)
  1115  }
  1116  
  1117  func (sw *fakeSubResourceClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error {
  1118  	patchOptions := client.SubResourcePatchOptions{}
  1119  	patchOptions.ApplyOptions(opts)
  1120  
  1121  	body := obj
  1122  	if patchOptions.SubResourceBody != nil {
  1123  		body = patchOptions.SubResourceBody
  1124  	}
  1125  
  1126  	// this is necessary to identify that last call was made for status patch, through stack trace.
  1127  	if sw.subResource == "status" {
  1128  		return sw.statusPatch(body, patch, patchOptions)
  1129  	}
  1130  
  1131  	return sw.client.patch(body, patch, &patchOptions.PatchOptions)
  1132  }
  1133  
  1134  func (sw *fakeSubResourceClient) statusPatch(body client.Object, patch client.Patch, patchOptions client.SubResourcePatchOptions) error {
  1135  	return sw.client.patch(body, patch, &patchOptions.PatchOptions)
  1136  }
  1137  
  1138  func allowsUnconditionalUpdate(gvk schema.GroupVersionKind) bool {
  1139  	switch gvk.Group {
  1140  	case "apps":
  1141  		switch gvk.Kind {
  1142  		case "ControllerRevision", "DaemonSet", "Deployment", "ReplicaSet", "StatefulSet":
  1143  			return true
  1144  		}
  1145  	case "autoscaling":
  1146  		switch gvk.Kind {
  1147  		case "HorizontalPodAutoscaler":
  1148  			return true
  1149  		}
  1150  	case "batch":
  1151  		switch gvk.Kind {
  1152  		case "CronJob", "Job":
  1153  			return true
  1154  		}
  1155  	case "certificates":
  1156  		switch gvk.Kind {
  1157  		case "Certificates":
  1158  			return true
  1159  		}
  1160  	case "flowcontrol":
  1161  		switch gvk.Kind {
  1162  		case "FlowSchema", "PriorityLevelConfiguration":
  1163  			return true
  1164  		}
  1165  	case "networking":
  1166  		switch gvk.Kind {
  1167  		case "Ingress", "IngressClass", "NetworkPolicy":
  1168  			return true
  1169  		}
  1170  	case "policy":
  1171  		switch gvk.Kind {
  1172  		case "PodSecurityPolicy":
  1173  			return true
  1174  		}
  1175  	case "rbac.authorization.k8s.io":
  1176  		switch gvk.Kind {
  1177  		case "ClusterRole", "ClusterRoleBinding", "Role", "RoleBinding":
  1178  			return true
  1179  		}
  1180  	case "scheduling":
  1181  		switch gvk.Kind {
  1182  		case "PriorityClass":
  1183  			return true
  1184  		}
  1185  	case "settings":
  1186  		switch gvk.Kind {
  1187  		case "PodPreset":
  1188  			return true
  1189  		}
  1190  	case "storage":
  1191  		switch gvk.Kind {
  1192  		case "StorageClass":
  1193  			return true
  1194  		}
  1195  	case "":
  1196  		switch gvk.Kind {
  1197  		case "ConfigMap", "Endpoint", "Event", "LimitRange", "Namespace", "Node",
  1198  			"PersistentVolume", "PersistentVolumeClaim", "Pod", "PodTemplate",
  1199  			"ReplicationController", "ResourceQuota", "Secret", "Service",
  1200  			"ServiceAccount", "EndpointSlice":
  1201  			return true
  1202  		}
  1203  	}
  1204  
  1205  	return false
  1206  }
  1207  
  1208  func allowsCreateOnUpdate(gvk schema.GroupVersionKind) bool {
  1209  	switch gvk.Group {
  1210  	case "coordination":
  1211  		switch gvk.Kind {
  1212  		case "Lease":
  1213  			return true
  1214  		}
  1215  	case "node":
  1216  		switch gvk.Kind {
  1217  		case "RuntimeClass":
  1218  			return true
  1219  		}
  1220  	case "rbac":
  1221  		switch gvk.Kind {
  1222  		case "ClusterRole", "ClusterRoleBinding", "Role", "RoleBinding":
  1223  			return true
  1224  		}
  1225  	case "":
  1226  		switch gvk.Kind {
  1227  		case "Endpoint", "Event", "LimitRange", "Service":
  1228  			return true
  1229  		}
  1230  	}
  1231  
  1232  	return false
  1233  }
  1234  
  1235  func inTreeResourcesWithStatus() []schema.GroupVersionKind {
  1236  	return []schema.GroupVersionKind{
  1237  		{Version: "v1", Kind: "Namespace"},
  1238  		{Version: "v1", Kind: "Node"},
  1239  		{Version: "v1", Kind: "PersistentVolumeClaim"},
  1240  		{Version: "v1", Kind: "PersistentVolume"},
  1241  		{Version: "v1", Kind: "Pod"},
  1242  		{Version: "v1", Kind: "ReplicationController"},
  1243  		{Version: "v1", Kind: "Service"},
  1244  
  1245  		{Group: "apps", Version: "v1", Kind: "Deployment"},
  1246  		{Group: "apps", Version: "v1", Kind: "DaemonSet"},
  1247  		{Group: "apps", Version: "v1", Kind: "ReplicaSet"},
  1248  		{Group: "apps", Version: "v1", Kind: "StatefulSet"},
  1249  
  1250  		{Group: "autoscaling", Version: "v1", Kind: "HorizontalPodAutoscaler"},
  1251  
  1252  		{Group: "batch", Version: "v1", Kind: "CronJob"},
  1253  		{Group: "batch", Version: "v1", Kind: "Job"},
  1254  
  1255  		{Group: "certificates.k8s.io", Version: "v1", Kind: "CertificateSigningRequest"},
  1256  
  1257  		{Group: "networking.k8s.io", Version: "v1", Kind: "Ingress"},
  1258  		{Group: "networking.k8s.io", Version: "v1", Kind: "NetworkPolicy"},
  1259  
  1260  		{Group: "policy", Version: "v1", Kind: "PodDisruptionBudget"},
  1261  
  1262  		{Group: "storage.k8s.io", Version: "v1", Kind: "VolumeAttachment"},
  1263  
  1264  		{Group: "apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"},
  1265  
  1266  		{Group: "flowcontrol.apiserver.k8s.io", Version: "v1beta2", Kind: "FlowSchema"},
  1267  		{Group: "flowcontrol.apiserver.k8s.io", Version: "v1beta2", Kind: "PriorityLevelConfiguration"},
  1268  		{Group: "flowcontrol.apiserver.k8s.io", Version: "v1", Kind: "FlowSchema"},
  1269  		{Group: "flowcontrol.apiserver.k8s.io", Version: "v1", Kind: "PriorityLevelConfiguration"},
  1270  	}
  1271  }
  1272  
  1273  // zero zeros the value of a pointer.
  1274  func zero(x interface{}) {
  1275  	if x == nil {
  1276  		return
  1277  	}
  1278  	res := reflect.ValueOf(x).Elem()
  1279  	res.Set(reflect.Zero(res.Type()))
  1280  }