k8s.io/apiserver@v0.31.1/pkg/admission/plugin/policy/generic/policy_test_context.go (about)

     1  /*
     2  Copyright 2024 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 generic
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	corev1 "k8s.io/api/core/v1"
    25  	"k8s.io/apimachinery/pkg/api/errors"
    26  	"k8s.io/apimachinery/pkg/api/meta"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	"k8s.io/apimachinery/pkg/runtime/schema"
    31  	"k8s.io/apimachinery/pkg/runtime/serializer"
    32  	"k8s.io/apimachinery/pkg/types"
    33  	"k8s.io/apimachinery/pkg/util/uuid"
    34  	"k8s.io/apimachinery/pkg/util/wait"
    35  	"k8s.io/apimachinery/pkg/watch"
    36  	"k8s.io/client-go/dynamic"
    37  	dynamicfake "k8s.io/client-go/dynamic/fake"
    38  	"k8s.io/client-go/informers"
    39  	"k8s.io/client-go/kubernetes"
    40  	"k8s.io/client-go/kubernetes/fake"
    41  	clienttesting "k8s.io/client-go/testing"
    42  	"k8s.io/client-go/tools/cache"
    43  	"k8s.io/component-base/featuregate"
    44  
    45  	"k8s.io/apiserver/pkg/admission"
    46  	"k8s.io/apiserver/pkg/admission/initializer"
    47  	"k8s.io/apiserver/pkg/authorization/authorizer"
    48  	"k8s.io/apiserver/pkg/features"
    49  )
    50  
    51  // PolicyTestContext is everything you need to unit test a policy plugin
    52  type PolicyTestContext[P runtime.Object, B runtime.Object, E Evaluator] struct {
    53  	context.Context
    54  	Plugin *Plugin[PolicyHook[P, B, E]]
    55  	Source Source[PolicyHook[P, B, E]]
    56  	Start  func() error
    57  
    58  	scheme     *runtime.Scheme
    59  	restMapper *meta.DefaultRESTMapper
    60  	policyGVR  schema.GroupVersionResource
    61  	bindingGVR schema.GroupVersionResource
    62  
    63  	policyGVK  schema.GroupVersionKind
    64  	bindingGVK schema.GroupVersionKind
    65  
    66  	nativeTracker           clienttesting.ObjectTracker
    67  	policyAndBindingTracker clienttesting.ObjectTracker
    68  	unstructuredTracker     clienttesting.ObjectTracker
    69  }
    70  
    71  func NewPolicyTestContext[P, B runtime.Object, E Evaluator](
    72  	newPolicyAccessor func(P) PolicyAccessor,
    73  	newBindingAccessor func(B) BindingAccessor,
    74  	compileFunc func(P) E,
    75  	dispatcher dispatcherFactory[PolicyHook[P, B, E]],
    76  	initialObjects []runtime.Object,
    77  	paramMappings []meta.RESTMapping,
    78  ) (*PolicyTestContext[P, B, E], func(), error) {
    79  	var Pexample P
    80  	var Bexample B
    81  
    82  	// Create a fake resource and kind for the provided policy and binding types
    83  	fakePolicyGVR := schema.GroupVersionResource{
    84  		Group:    "policy.example.com",
    85  		Version:  "v1",
    86  		Resource: "fakepolicies",
    87  	}
    88  	fakeBindingGVR := schema.GroupVersionResource{
    89  		Group:    "policy.example.com",
    90  		Version:  "v1",
    91  		Resource: "fakebindings",
    92  	}
    93  	fakePolicyGVK := fakePolicyGVR.GroupVersion().WithKind("FakePolicy")
    94  	fakeBindingGVK := fakeBindingGVR.GroupVersion().WithKind("FakeBinding")
    95  
    96  	policySourceTestScheme, err := func() (*runtime.Scheme, error) {
    97  		scheme := runtime.NewScheme()
    98  
    99  		if err := fake.AddToScheme(scheme); err != nil {
   100  			return nil, err
   101  		}
   102  
   103  		scheme.AddKnownTypeWithName(fakePolicyGVK, Pexample)
   104  		scheme.AddKnownTypeWithName(fakeBindingGVK, Bexample)
   105  		scheme.AddKnownTypeWithName(fakePolicyGVK.GroupVersion().WithKind(fakePolicyGVK.Kind+"List"), &FakeList[P]{})
   106  		scheme.AddKnownTypeWithName(fakeBindingGVK.GroupVersion().WithKind(fakeBindingGVK.Kind+"List"), &FakeList[B]{})
   107  
   108  		for _, mapping := range paramMappings {
   109  			// Skip if it is in the scheme already
   110  			if scheme.Recognizes(mapping.GroupVersionKind) {
   111  				continue
   112  			}
   113  			scheme.AddKnownTypeWithName(mapping.GroupVersionKind, &unstructured.Unstructured{})
   114  			scheme.AddKnownTypeWithName(mapping.GroupVersionKind.GroupVersion().WithKind(mapping.GroupVersionKind.Kind+"List"), &unstructured.UnstructuredList{})
   115  		}
   116  
   117  		return scheme, nil
   118  	}()
   119  	if err != nil {
   120  		return nil, nil, err
   121  	}
   122  
   123  	fakeRestMapper := func() *meta.DefaultRESTMapper {
   124  		res := meta.NewDefaultRESTMapper([]schema.GroupVersion{
   125  			{
   126  				Group:   "",
   127  				Version: "v1",
   128  			},
   129  		})
   130  
   131  		res.Add(fakePolicyGVK, meta.RESTScopeRoot)
   132  		res.Add(fakeBindingGVK, meta.RESTScopeRoot)
   133  		res.Add(corev1.SchemeGroupVersion.WithKind("ConfigMap"), meta.RESTScopeNamespace)
   134  
   135  		for _, mapping := range paramMappings {
   136  			res.AddSpecific(mapping.GroupVersionKind, mapping.Resource, mapping.Resource, mapping.Scope)
   137  		}
   138  
   139  		return res
   140  	}()
   141  
   142  	nativeClient := fake.NewSimpleClientset()
   143  	dynamicClient := dynamicfake.NewSimpleDynamicClient(policySourceTestScheme)
   144  	fakeInformerFactory := informers.NewSharedInformerFactory(nativeClient, 30*time.Second)
   145  
   146  	// Make an object tracker specifically for our policies and bindings
   147  	policiesAndBindingsTracker := clienttesting.NewObjectTracker(
   148  		policySourceTestScheme,
   149  		serializer.NewCodecFactory(policySourceTestScheme).UniversalDecoder())
   150  
   151  	// Make an informer for our policies and bindings
   152  
   153  	policyInformer := cache.NewSharedIndexInformer(
   154  		&cache.ListWatch{
   155  			ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   156  				return policiesAndBindingsTracker.List(fakePolicyGVR, fakePolicyGVK, "")
   157  			},
   158  			WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   159  				return policiesAndBindingsTracker.Watch(fakePolicyGVR, "")
   160  			},
   161  		},
   162  		Pexample,
   163  		30*time.Second,
   164  		cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
   165  	)
   166  	bindingInformer := cache.NewSharedIndexInformer(
   167  		&cache.ListWatch{
   168  			ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   169  				return policiesAndBindingsTracker.List(fakeBindingGVR, fakeBindingGVK, "")
   170  			},
   171  			WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   172  				return policiesAndBindingsTracker.Watch(fakeBindingGVR, "")
   173  			},
   174  		},
   175  		Bexample,
   176  		30*time.Second,
   177  		cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
   178  	)
   179  
   180  	var source Source[PolicyHook[P, B, E]]
   181  	plugin := NewPlugin[PolicyHook[P, B, E]](
   182  		admission.NewHandler(admission.Connect, admission.Create, admission.Delete, admission.Update),
   183  		func(sif informers.SharedInformerFactory, i1 kubernetes.Interface, i2 dynamic.Interface, r meta.RESTMapper) Source[PolicyHook[P, B, E]] {
   184  			source = NewPolicySource[P, B, E](
   185  				policyInformer,
   186  				bindingInformer,
   187  				newPolicyAccessor,
   188  				newBindingAccessor,
   189  				compileFunc,
   190  				sif,
   191  				i2,
   192  				r,
   193  			)
   194  			return source
   195  		}, dispatcher)
   196  	plugin.SetEnabled(true)
   197  
   198  	featureGate := featuregate.NewFeatureGate()
   199  	err = featureGate.Add(map[featuregate.Feature]featuregate.FeatureSpec{
   200  		//!TODO: move this to validating specific tests
   201  		features.ValidatingAdmissionPolicy: {
   202  			Default: true, PreRelease: featuregate.Beta}})
   203  	if err != nil {
   204  		return nil, nil, err
   205  	}
   206  	err = featureGate.SetFromMap(map[string]bool{string(features.ValidatingAdmissionPolicy): true})
   207  	if err != nil {
   208  		return nil, nil, err
   209  	}
   210  
   211  	testContext, testCancel := context.WithCancel(context.Background())
   212  	genericInitializer := initializer.New(
   213  		nativeClient,
   214  		dynamicClient,
   215  		fakeInformerFactory,
   216  		fakeAuthorizer{},
   217  		featureGate,
   218  		testContext.Done(),
   219  		fakeRestMapper,
   220  	)
   221  	genericInitializer.Initialize(plugin)
   222  	plugin.SetRESTMapper(fakeRestMapper)
   223  
   224  	if err := plugin.ValidateInitialization(); err != nil {
   225  		testCancel()
   226  		return nil, nil, err
   227  	}
   228  
   229  	res := &PolicyTestContext[P, B, E]{
   230  		Context: testContext,
   231  		Plugin:  plugin,
   232  		Source:  source,
   233  
   234  		restMapper:              fakeRestMapper,
   235  		scheme:                  policySourceTestScheme,
   236  		policyGVK:               fakePolicyGVK,
   237  		bindingGVK:              fakeBindingGVK,
   238  		policyGVR:               fakePolicyGVR,
   239  		bindingGVR:              fakeBindingGVR,
   240  		nativeTracker:           nativeClient.Tracker(),
   241  		policyAndBindingTracker: policiesAndBindingsTracker,
   242  		unstructuredTracker:     dynamicClient.Tracker(),
   243  	}
   244  
   245  	for _, obj := range initialObjects {
   246  		err := res.updateOne(obj)
   247  		if err != nil {
   248  			testCancel()
   249  			return nil, nil, err
   250  		}
   251  	}
   252  
   253  	res.Start = func() error {
   254  		fakeInformerFactory.Start(res.Done())
   255  		go policyInformer.Run(res.Done())
   256  		go bindingInformer.Run(res.Done())
   257  
   258  		if !cache.WaitForCacheSync(res.Done(), res.Source.HasSynced) {
   259  			return fmt.Errorf("timed out waiting for initial cache sync")
   260  		}
   261  		return nil
   262  	}
   263  	return res, testCancel, nil
   264  }
   265  
   266  // UpdateAndWait updates the given object in the test, or creates it if it doesn't exist
   267  // Depending upon object type, waits afterward until the object is synced
   268  // by the policy source
   269  //
   270  // Be aware the UpdateAndWait will modify the ResourceVersion of the
   271  // provided objects.
   272  func (p *PolicyTestContext[P, B, E]) UpdateAndWait(objects ...runtime.Object) error {
   273  	return p.update(true, objects...)
   274  }
   275  
   276  // Update updates the given object in the test, or creates it if it doesn't exist
   277  //
   278  // Be aware the Update will modify the ResourceVersion of the
   279  // provided objects.
   280  func (p *PolicyTestContext[P, B, E]) Update(objects ...runtime.Object) error {
   281  	return p.update(false, objects...)
   282  }
   283  
   284  // Objects the given object in the test, or creates it if it doesn't exist
   285  // Depending upon object type, waits afterward until the object is synced
   286  // by the policy source
   287  func (p *PolicyTestContext[P, B, E]) update(wait bool, objects ...runtime.Object) error {
   288  	for _, object := range objects {
   289  		if err := p.updateOne(object); err != nil {
   290  			return err
   291  		}
   292  	}
   293  
   294  	if wait {
   295  		timeoutCtx, timeoutCancel := context.WithTimeout(p, 3*time.Second)
   296  		defer timeoutCancel()
   297  
   298  		for _, object := range objects {
   299  			if err := p.WaitForReconcile(timeoutCtx, object); err != nil {
   300  				return fmt.Errorf("error waiting for reconcile of %v: %v", object, err)
   301  			}
   302  		}
   303  	}
   304  	return nil
   305  }
   306  
   307  // Depending upon object type, waits afterward until the object is synced
   308  // by the policy source. Note that policies that are not bound are skipped,
   309  // so you should not try to wait for an unbound policy. Create both the binding
   310  // and policy, then wait.
   311  func (p *PolicyTestContext[P, B, E]) WaitForReconcile(timeoutCtx context.Context, object runtime.Object) error {
   312  	if !p.Source.HasSynced() {
   313  		return nil
   314  	}
   315  
   316  	objectMeta, err := meta.Accessor(object)
   317  	if err != nil {
   318  		return err
   319  	}
   320  
   321  	objectGVK, _, err := p.inferGVK(object)
   322  	if err != nil {
   323  		return err
   324  	}
   325  
   326  	switch objectGVK {
   327  	case p.policyGVK:
   328  		return wait.PollUntilContextCancel(timeoutCtx, 100*time.Millisecond, true, func(ctx context.Context) (done bool, err error) {
   329  			policies := p.Source.Hooks()
   330  			for _, policy := range policies {
   331  				policyMeta, err := meta.Accessor(policy.Policy)
   332  				if err != nil {
   333  					return true, err
   334  				} else if policyMeta.GetName() == objectMeta.GetName() && policyMeta.GetResourceVersion() == objectMeta.GetResourceVersion() {
   335  					return true, nil
   336  				}
   337  			}
   338  			return false, nil
   339  		})
   340  	case p.bindingGVK:
   341  		return wait.PollUntilContextCancel(timeoutCtx, 100*time.Millisecond, true, func(ctx context.Context) (done bool, err error) {
   342  			policies := p.Source.Hooks()
   343  			for _, policy := range policies {
   344  				for _, binding := range policy.Bindings {
   345  					bindingMeta, err := meta.Accessor(binding)
   346  					if err != nil {
   347  						return true, err
   348  					} else if bindingMeta.GetName() == objectMeta.GetName() && bindingMeta.GetResourceVersion() == objectMeta.GetResourceVersion() {
   349  						return true, nil
   350  					}
   351  				}
   352  			}
   353  			return false, nil
   354  		})
   355  
   356  	default:
   357  		// Do nothing, params are visible immediately
   358  		// Loop until one of the params is visible via get of the param informer
   359  		return wait.PollUntilContextCancel(timeoutCtx, 100*time.Millisecond, true, func(ctx context.Context) (done bool, err error) {
   360  			informer, scope := p.Source.(*policySource[P, B, E]).getParamInformer(objectGVK)
   361  			if informer == nil {
   362  				// Informer does not exist yet, keep waiting for sync
   363  				return false, nil
   364  			}
   365  
   366  			if !cache.WaitForCacheSync(timeoutCtx.Done(), informer.Informer().HasSynced) {
   367  				return false, fmt.Errorf("timed out waiting for cache sync of param informer")
   368  			}
   369  
   370  			var lister cache.GenericNamespaceLister = informer.Lister()
   371  			if scope == meta.RESTScopeNamespace {
   372  				lister = informer.Lister().ByNamespace(objectMeta.GetNamespace())
   373  			}
   374  
   375  			fetched, err := lister.Get(objectMeta.GetName())
   376  			if err != nil {
   377  				if errors.IsNotFound(err) {
   378  					return false, nil
   379  				}
   380  				return true, err
   381  			}
   382  
   383  			// Ensure RV matches
   384  			fetchedMeta, err := meta.Accessor(fetched)
   385  			if err != nil {
   386  				return true, err
   387  			} else if fetchedMeta.GetResourceVersion() != objectMeta.GetResourceVersion() {
   388  				return false, nil
   389  			}
   390  
   391  			return true, nil
   392  		})
   393  	}
   394  }
   395  
   396  func (p *PolicyTestContext[P, B, E]) waitForDelete(ctx context.Context, objectGVK schema.GroupVersionKind, name types.NamespacedName) error {
   397  	srce := p.Source.(*policySource[P, B, E])
   398  
   399  	return wait.PollUntilContextCancel(ctx, 100*time.Millisecond, true, func(ctx context.Context) (done bool, err error) {
   400  		switch objectGVK {
   401  		case p.policyGVK:
   402  			for _, hook := range p.Source.Hooks() {
   403  				accessor := srce.newPolicyAccessor(hook.Policy)
   404  				if accessor.GetName() == name.Name && accessor.GetNamespace() == name.Namespace {
   405  					return false, nil
   406  				}
   407  			}
   408  
   409  			return true, nil
   410  		case p.bindingGVK:
   411  			for _, hook := range p.Source.Hooks() {
   412  				for _, binding := range hook.Bindings {
   413  					accessor := srce.newBindingAccessor(binding)
   414  					if accessor.GetName() == name.Name && accessor.GetNamespace() == name.Namespace {
   415  						return false, nil
   416  					}
   417  				}
   418  			}
   419  			return true, nil
   420  		default:
   421  			// Do nothing, params are visible immediately
   422  			// Loop until one of the params is visible via get of the param informer
   423  			informer, scope := p.Source.(*policySource[P, B, E]).getParamInformer(objectGVK)
   424  			if informer == nil {
   425  				return true, nil
   426  			}
   427  
   428  			var lister cache.GenericNamespaceLister = informer.Lister()
   429  			if scope == meta.RESTScopeNamespace {
   430  				lister = informer.Lister().ByNamespace(name.Namespace)
   431  			}
   432  
   433  			_, err = lister.Get(name.Name)
   434  			if err != nil {
   435  				if errors.IsNotFound(err) {
   436  					return true, nil
   437  				}
   438  				return false, err
   439  			}
   440  			return false, nil
   441  		}
   442  	})
   443  }
   444  
   445  func (p *PolicyTestContext[P, B, E]) updateOne(object runtime.Object) error {
   446  	objectMeta, err := meta.Accessor(object)
   447  	if err != nil {
   448  		return err
   449  	}
   450  	objectMeta.SetResourceVersion(string(uuid.NewUUID()))
   451  	objectGVK, gvr, err := p.inferGVK(object)
   452  	if err != nil {
   453  		return err
   454  	}
   455  
   456  	switch objectGVK {
   457  	case p.policyGVK:
   458  		err := p.policyAndBindingTracker.Update(p.policyGVR, object, objectMeta.GetNamespace())
   459  		if errors.IsNotFound(err) {
   460  			err = p.policyAndBindingTracker.Create(p.policyGVR, object, objectMeta.GetNamespace())
   461  		}
   462  
   463  		return err
   464  	case p.bindingGVK:
   465  		err := p.policyAndBindingTracker.Update(p.bindingGVR, object, objectMeta.GetNamespace())
   466  		if errors.IsNotFound(err) {
   467  			err = p.policyAndBindingTracker.Create(p.bindingGVR, object, objectMeta.GetNamespace())
   468  		}
   469  
   470  		return err
   471  	default:
   472  		if _, ok := object.(*unstructured.Unstructured); ok {
   473  			if err := p.unstructuredTracker.Create(gvr, object, objectMeta.GetNamespace()); err != nil {
   474  				if errors.IsAlreadyExists(err) {
   475  					return p.unstructuredTracker.Update(gvr, object, objectMeta.GetNamespace())
   476  				}
   477  				return err
   478  			}
   479  			return nil
   480  		} else if err := p.nativeTracker.Create(gvr, object, objectMeta.GetNamespace()); err != nil {
   481  			if errors.IsAlreadyExists(err) {
   482  				return p.nativeTracker.Update(gvr, object, objectMeta.GetNamespace())
   483  			}
   484  		}
   485  		return nil
   486  	}
   487  }
   488  
   489  // Depending upon object type, waits afterward until the object is synced
   490  // by the policy source
   491  func (p *PolicyTestContext[P, B, E]) DeleteAndWait(object ...runtime.Object) error {
   492  	for _, object := range object {
   493  		if err := p.deleteOne(object); err != nil && !errors.IsNotFound(err) {
   494  			return err
   495  		}
   496  	}
   497  
   498  	timeoutCtx, timeoutCancel := context.WithTimeout(p, 3*time.Second)
   499  	defer timeoutCancel()
   500  
   501  	for _, object := range object {
   502  		accessor, err := meta.Accessor(object)
   503  		if err != nil {
   504  			return err
   505  		}
   506  
   507  		objectGVK, _, err := p.inferGVK(object)
   508  		if err != nil {
   509  			return err
   510  		}
   511  
   512  		if err := p.waitForDelete(
   513  			timeoutCtx,
   514  			objectGVK,
   515  			types.NamespacedName{Name: accessor.GetName(), Namespace: accessor.GetNamespace()}); err != nil {
   516  			return err
   517  		}
   518  	}
   519  	return nil
   520  }
   521  
   522  func (p *PolicyTestContext[P, B, E]) deleteOne(object runtime.Object) error {
   523  	objectMeta, err := meta.Accessor(object)
   524  	if err != nil {
   525  		return err
   526  	}
   527  	objectMeta.SetResourceVersion(string(uuid.NewUUID()))
   528  	objectGVK, gvr, err := p.inferGVK(object)
   529  	if err != nil {
   530  		return err
   531  	}
   532  
   533  	switch objectGVK {
   534  	case p.policyGVK:
   535  		return p.policyAndBindingTracker.Delete(p.policyGVR, objectMeta.GetNamespace(), objectMeta.GetName())
   536  	case p.bindingGVK:
   537  		return p.policyAndBindingTracker.Delete(p.bindingGVR, objectMeta.GetNamespace(), objectMeta.GetName())
   538  	default:
   539  		if _, ok := object.(*unstructured.Unstructured); ok {
   540  			return p.unstructuredTracker.Delete(gvr, objectMeta.GetNamespace(), objectMeta.GetName())
   541  		}
   542  		return p.nativeTracker.Delete(gvr, objectMeta.GetNamespace(), objectMeta.GetName())
   543  	}
   544  }
   545  
   546  func (p *PolicyTestContext[P, B, E]) Dispatch(
   547  	new, old runtime.Object,
   548  	operation admission.Operation,
   549  ) error {
   550  	if old == nil && new == nil {
   551  		return fmt.Errorf("both old and new objects cannot be nil")
   552  	}
   553  
   554  	nonNilObject := new
   555  	if nonNilObject == nil {
   556  		nonNilObject = old
   557  	}
   558  
   559  	gvk, gvr, err := p.inferGVK(nonNilObject)
   560  	if err != nil {
   561  		return err
   562  	}
   563  
   564  	nonNilMeta, err := meta.Accessor(nonNilObject)
   565  	if err != nil {
   566  		return err
   567  	}
   568  
   569  	return p.Plugin.Dispatch(
   570  		p,
   571  		admission.NewAttributesRecord(
   572  			new,
   573  			old,
   574  			gvk,
   575  			nonNilMeta.GetName(),
   576  			nonNilMeta.GetNamespace(),
   577  			gvr,
   578  			"",
   579  			operation,
   580  			nil,
   581  			false,
   582  			nil,
   583  		), admission.NewObjectInterfacesFromScheme(p.scheme))
   584  }
   585  
   586  func (p *PolicyTestContext[P, B, E]) inferGVK(object runtime.Object) (schema.GroupVersionKind, schema.GroupVersionResource, error) {
   587  	objectGVK := object.GetObjectKind().GroupVersionKind()
   588  	if objectGVK.Empty() {
   589  		// If the object doesn't have a GVK, ask the schema for preferred GVK
   590  		knownKinds, _, err := p.scheme.ObjectKinds(object)
   591  		if err != nil {
   592  			return schema.GroupVersionKind{}, schema.GroupVersionResource{}, err
   593  		} else if len(knownKinds) == 0 {
   594  			return schema.GroupVersionKind{}, schema.GroupVersionResource{}, fmt.Errorf("no known GVKs for object in schema: %T", object)
   595  		}
   596  		toTake := 0
   597  
   598  		// Prefer GVK if it is our fake policy or binding
   599  		for i, knownKind := range knownKinds {
   600  			if knownKind == p.policyGVK || knownKind == p.bindingGVK {
   601  				toTake = i
   602  				break
   603  			}
   604  		}
   605  
   606  		objectGVK = knownKinds[toTake]
   607  	}
   608  
   609  	// Make sure GVK is known to the fake rest mapper. To prevent cryptic error
   610  	mapping, err := p.restMapper.RESTMapping(objectGVK.GroupKind(), objectGVK.Version)
   611  	if err != nil {
   612  		return schema.GroupVersionKind{}, schema.GroupVersionResource{}, err
   613  	}
   614  	return objectGVK, mapping.Resource, nil
   615  }
   616  
   617  type FakeList[T runtime.Object] struct {
   618  	metav1.TypeMeta
   619  	metav1.ListMeta
   620  	Items []T
   621  }
   622  
   623  func (fl *FakeList[P]) DeepCopyObject() runtime.Object {
   624  	copiedItems := make([]P, len(fl.Items))
   625  	for i, item := range fl.Items {
   626  		copiedItems[i] = item.DeepCopyObject().(P)
   627  	}
   628  	return &FakeList[P]{
   629  		TypeMeta: fl.TypeMeta,
   630  		ListMeta: fl.ListMeta,
   631  		Items:    copiedItems,
   632  	}
   633  }
   634  
   635  type fakeAuthorizer struct{}
   636  
   637  func (f fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
   638  	return authorizer.DecisionAllow, "", nil
   639  }