627 lines
19 KiB
Go
627 lines
19 KiB
Go
/*
|
|
Copyright 2024 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 generic
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/errors"
|
|
"k8s.io/apimachinery/pkg/api/meta"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"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/util/uuid"
|
|
"k8s.io/apimachinery/pkg/util/wait"
|
|
"k8s.io/apimachinery/pkg/watch"
|
|
"k8s.io/client-go/dynamic"
|
|
dynamicfake "k8s.io/client-go/dynamic/fake"
|
|
"k8s.io/client-go/informers"
|
|
"k8s.io/client-go/kubernetes"
|
|
"k8s.io/client-go/kubernetes/fake"
|
|
clienttesting "k8s.io/client-go/testing"
|
|
"k8s.io/client-go/tools/cache"
|
|
"k8s.io/component-base/featuregate"
|
|
|
|
"k8s.io/apiserver/pkg/admission"
|
|
"k8s.io/apiserver/pkg/admission/initializer"
|
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
|
)
|
|
|
|
// PolicyTestContext is everything you need to unit test a policy plugin
|
|
type PolicyTestContext[P runtime.Object, B runtime.Object, E Evaluator] struct {
|
|
context.Context
|
|
Plugin *Plugin[PolicyHook[P, B, E]]
|
|
Source Source[PolicyHook[P, B, E]]
|
|
Start func() error
|
|
|
|
scheme *runtime.Scheme
|
|
restMapper *meta.DefaultRESTMapper
|
|
policyGVR schema.GroupVersionResource
|
|
bindingGVR schema.GroupVersionResource
|
|
|
|
policyGVK schema.GroupVersionKind
|
|
bindingGVK schema.GroupVersionKind
|
|
|
|
nativeTracker clienttesting.ObjectTracker
|
|
policyAndBindingTracker clienttesting.ObjectTracker
|
|
unstructuredTracker clienttesting.ObjectTracker
|
|
}
|
|
|
|
func NewPolicyTestContext[P, B runtime.Object, E Evaluator](
|
|
newPolicyAccessor func(P) PolicyAccessor,
|
|
newBindingAccessor func(B) BindingAccessor,
|
|
compileFunc func(P) E,
|
|
dispatcher dispatcherFactory[PolicyHook[P, B, E]],
|
|
initialObjects []runtime.Object,
|
|
paramMappings []meta.RESTMapping,
|
|
) (*PolicyTestContext[P, B, E], func(), error) {
|
|
var Pexample P
|
|
var Bexample B
|
|
|
|
// Create a fake resource and kind for the provided policy and binding types
|
|
fakePolicyGVR := schema.GroupVersionResource{
|
|
Group: "policy.example.com",
|
|
Version: "v1",
|
|
Resource: "fakepolicies",
|
|
}
|
|
fakeBindingGVR := schema.GroupVersionResource{
|
|
Group: "policy.example.com",
|
|
Version: "v1",
|
|
Resource: "fakebindings",
|
|
}
|
|
fakePolicyGVK := fakePolicyGVR.GroupVersion().WithKind("FakePolicy")
|
|
fakeBindingGVK := fakeBindingGVR.GroupVersion().WithKind("FakeBinding")
|
|
|
|
policySourceTestScheme, err := func() (*runtime.Scheme, error) {
|
|
scheme := runtime.NewScheme()
|
|
|
|
if err := fake.AddToScheme(scheme); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
scheme.AddKnownTypeWithName(fakePolicyGVK, Pexample)
|
|
scheme.AddKnownTypeWithName(fakeBindingGVK, Bexample)
|
|
scheme.AddKnownTypeWithName(fakePolicyGVK.GroupVersion().WithKind(fakePolicyGVK.Kind+"List"), &FakeList[P]{})
|
|
scheme.AddKnownTypeWithName(fakeBindingGVK.GroupVersion().WithKind(fakeBindingGVK.Kind+"List"), &FakeList[B]{})
|
|
|
|
for _, mapping := range paramMappings {
|
|
// Skip if it is in the scheme already
|
|
if scheme.Recognizes(mapping.GroupVersionKind) {
|
|
continue
|
|
}
|
|
scheme.AddKnownTypeWithName(mapping.GroupVersionKind, &unstructured.Unstructured{})
|
|
scheme.AddKnownTypeWithName(mapping.GroupVersionKind.GroupVersion().WithKind(mapping.GroupVersionKind.Kind+"List"), &unstructured.UnstructuredList{})
|
|
}
|
|
|
|
return scheme, nil
|
|
}()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
fakeRestMapper := func() *meta.DefaultRESTMapper {
|
|
res := meta.NewDefaultRESTMapper([]schema.GroupVersion{
|
|
{
|
|
Group: "",
|
|
Version: "v1",
|
|
},
|
|
})
|
|
|
|
res.Add(fakePolicyGVK, meta.RESTScopeRoot)
|
|
res.Add(fakeBindingGVK, meta.RESTScopeRoot)
|
|
res.Add(corev1.SchemeGroupVersion.WithKind("ConfigMap"), meta.RESTScopeNamespace)
|
|
|
|
for _, mapping := range paramMappings {
|
|
res.AddSpecific(mapping.GroupVersionKind, mapping.Resource, mapping.Resource, mapping.Scope)
|
|
}
|
|
|
|
return res
|
|
}()
|
|
|
|
nativeClient := fake.NewSimpleClientset()
|
|
dynamicClient := dynamicfake.NewSimpleDynamicClient(policySourceTestScheme)
|
|
fakeInformerFactory := informers.NewSharedInformerFactory(nativeClient, 30*time.Second)
|
|
|
|
// Make an object tracker specifically for our policies and bindings
|
|
policiesAndBindingsTracker := clienttesting.NewObjectTracker(
|
|
policySourceTestScheme,
|
|
serializer.NewCodecFactory(policySourceTestScheme).UniversalDecoder())
|
|
|
|
// Make an informer for our policies and bindings
|
|
|
|
policyInformer := cache.NewSharedIndexInformer(
|
|
&cache.ListWatch{
|
|
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
|
return policiesAndBindingsTracker.List(fakePolicyGVR, fakePolicyGVK, "")
|
|
},
|
|
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
|
return policiesAndBindingsTracker.Watch(fakePolicyGVR, "")
|
|
},
|
|
},
|
|
Pexample,
|
|
30*time.Second,
|
|
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
|
|
)
|
|
bindingInformer := cache.NewSharedIndexInformer(
|
|
&cache.ListWatch{
|
|
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
|
return policiesAndBindingsTracker.List(fakeBindingGVR, fakeBindingGVK, "")
|
|
},
|
|
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
|
return policiesAndBindingsTracker.Watch(fakeBindingGVR, "")
|
|
},
|
|
},
|
|
Bexample,
|
|
30*time.Second,
|
|
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
|
|
)
|
|
|
|
var source Source[PolicyHook[P, B, E]]
|
|
plugin := NewPlugin[PolicyHook[P, B, E]](
|
|
admission.NewHandler(admission.Connect, admission.Create, admission.Delete, admission.Update),
|
|
func(sif informers.SharedInformerFactory, i1 kubernetes.Interface, i2 dynamic.Interface, r meta.RESTMapper) Source[PolicyHook[P, B, E]] {
|
|
source = NewPolicySource[P, B, E](
|
|
policyInformer,
|
|
bindingInformer,
|
|
newPolicyAccessor,
|
|
newBindingAccessor,
|
|
compileFunc,
|
|
sif,
|
|
i2,
|
|
r,
|
|
)
|
|
return source
|
|
}, dispatcher)
|
|
plugin.SetEnabled(true)
|
|
|
|
featureGate := featuregate.NewFeatureGate()
|
|
testContext, testCancel := context.WithCancel(context.Background())
|
|
genericInitializer := initializer.New(
|
|
nativeClient,
|
|
dynamicClient,
|
|
fakeInformerFactory,
|
|
fakeAuthorizer{},
|
|
featureGate,
|
|
testContext.Done(),
|
|
fakeRestMapper,
|
|
)
|
|
genericInitializer.Initialize(plugin)
|
|
plugin.SetRESTMapper(fakeRestMapper)
|
|
|
|
if err := plugin.ValidateInitialization(); err != nil {
|
|
testCancel()
|
|
return nil, nil, err
|
|
}
|
|
|
|
res := &PolicyTestContext[P, B, E]{
|
|
Context: testContext,
|
|
Plugin: plugin,
|
|
Source: source,
|
|
|
|
restMapper: fakeRestMapper,
|
|
scheme: policySourceTestScheme,
|
|
policyGVK: fakePolicyGVK,
|
|
bindingGVK: fakeBindingGVK,
|
|
policyGVR: fakePolicyGVR,
|
|
bindingGVR: fakeBindingGVR,
|
|
nativeTracker: nativeClient.Tracker(),
|
|
policyAndBindingTracker: policiesAndBindingsTracker,
|
|
unstructuredTracker: dynamicClient.Tracker(),
|
|
}
|
|
|
|
for _, obj := range initialObjects {
|
|
err := res.updateOne(obj)
|
|
if err != nil {
|
|
testCancel()
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
res.Start = func() error {
|
|
fakeInformerFactory.Start(res.Done())
|
|
go policyInformer.Run(res.Done())
|
|
go bindingInformer.Run(res.Done())
|
|
|
|
if !cache.WaitForCacheSync(res.Done(), res.Source.HasSynced) {
|
|
return fmt.Errorf("timed out waiting for initial cache sync")
|
|
}
|
|
return nil
|
|
}
|
|
return res, testCancel, nil
|
|
}
|
|
|
|
// UpdateAndWait updates the given object in the test, or creates it if it doesn't exist
|
|
// Depending upon object type, waits afterward until the object is synced
|
|
// by the policy source
|
|
//
|
|
// Be aware the UpdateAndWait will modify the ResourceVersion of the
|
|
// provided objects.
|
|
func (p *PolicyTestContext[P, B, E]) UpdateAndWait(objects ...runtime.Object) error {
|
|
return p.update(true, objects...)
|
|
}
|
|
|
|
// Update updates the given object in the test, or creates it if it doesn't exist
|
|
//
|
|
// Be aware the Update will modify the ResourceVersion of the
|
|
// provided objects.
|
|
func (p *PolicyTestContext[P, B, E]) Update(objects ...runtime.Object) error {
|
|
return p.update(false, objects...)
|
|
}
|
|
|
|
// Objects the given object in the test, or creates it if it doesn't exist
|
|
// Depending upon object type, waits afterward until the object is synced
|
|
// by the policy source
|
|
func (p *PolicyTestContext[P, B, E]) update(wait bool, objects ...runtime.Object) error {
|
|
for _, object := range objects {
|
|
if err := p.updateOne(object); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if wait {
|
|
timeoutCtx, timeoutCancel := context.WithTimeout(p, 3*time.Second)
|
|
defer timeoutCancel()
|
|
|
|
for _, object := range objects {
|
|
if err := p.WaitForReconcile(timeoutCtx, object); err != nil {
|
|
return fmt.Errorf("error waiting for reconcile of %v: %v", object, err)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Depending upon object type, waits afterward until the object is synced
|
|
// by the policy source. Note that policies that are not bound are skipped,
|
|
// so you should not try to wait for an unbound policy. Create both the binding
|
|
// and policy, then wait.
|
|
func (p *PolicyTestContext[P, B, E]) WaitForReconcile(timeoutCtx context.Context, object runtime.Object) error {
|
|
if !p.Source.HasSynced() {
|
|
return nil
|
|
}
|
|
|
|
objectMeta, err := meta.Accessor(object)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
objectGVK, _, err := p.inferGVK(object)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch objectGVK {
|
|
case p.policyGVK:
|
|
return wait.PollUntilContextCancel(timeoutCtx, 100*time.Millisecond, true, func(ctx context.Context) (done bool, err error) {
|
|
policies := p.Source.Hooks()
|
|
for _, policy := range policies {
|
|
policyMeta, err := meta.Accessor(policy.Policy)
|
|
if err != nil {
|
|
return true, err
|
|
} else if policyMeta.GetName() == objectMeta.GetName() && policyMeta.GetResourceVersion() == objectMeta.GetResourceVersion() {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
})
|
|
case p.bindingGVK:
|
|
return wait.PollUntilContextCancel(timeoutCtx, 100*time.Millisecond, true, func(ctx context.Context) (done bool, err error) {
|
|
policies := p.Source.Hooks()
|
|
for _, policy := range policies {
|
|
for _, binding := range policy.Bindings {
|
|
bindingMeta, err := meta.Accessor(binding)
|
|
if err != nil {
|
|
return true, err
|
|
} else if bindingMeta.GetName() == objectMeta.GetName() && bindingMeta.GetResourceVersion() == objectMeta.GetResourceVersion() {
|
|
return true, nil
|
|
}
|
|
}
|
|
}
|
|
return false, nil
|
|
})
|
|
|
|
default:
|
|
// Do nothing, params are visible immediately
|
|
// Loop until one of the params is visible via get of the param informer
|
|
return wait.PollUntilContextCancel(timeoutCtx, 100*time.Millisecond, true, func(ctx context.Context) (done bool, err error) {
|
|
informer, scope := p.Source.(*policySource[P, B, E]).getParamInformer(objectGVK)
|
|
if informer == nil {
|
|
// Informer does not exist yet, keep waiting for sync
|
|
return false, nil
|
|
}
|
|
|
|
if !cache.WaitForCacheSync(timeoutCtx.Done(), informer.Informer().HasSynced) {
|
|
return false, fmt.Errorf("timed out waiting for cache sync of param informer")
|
|
}
|
|
|
|
var lister cache.GenericNamespaceLister = informer.Lister()
|
|
if scope == meta.RESTScopeNamespace {
|
|
lister = informer.Lister().ByNamespace(objectMeta.GetNamespace())
|
|
}
|
|
|
|
fetched, err := lister.Get(objectMeta.GetName())
|
|
if err != nil {
|
|
if errors.IsNotFound(err) {
|
|
return false, nil
|
|
}
|
|
return true, err
|
|
}
|
|
|
|
// Ensure RV matches
|
|
fetchedMeta, err := meta.Accessor(fetched)
|
|
if err != nil {
|
|
return true, err
|
|
} else if fetchedMeta.GetResourceVersion() != objectMeta.GetResourceVersion() {
|
|
return false, nil
|
|
}
|
|
|
|
return true, nil
|
|
})
|
|
}
|
|
}
|
|
|
|
func (p *PolicyTestContext[P, B, E]) waitForDelete(ctx context.Context, objectGVK schema.GroupVersionKind, name types.NamespacedName) error {
|
|
srce := p.Source.(*policySource[P, B, E])
|
|
|
|
return wait.PollUntilContextCancel(ctx, 100*time.Millisecond, true, func(ctx context.Context) (done bool, err error) {
|
|
switch objectGVK {
|
|
case p.policyGVK:
|
|
for _, hook := range p.Source.Hooks() {
|
|
accessor := srce.newPolicyAccessor(hook.Policy)
|
|
if accessor.GetName() == name.Name && accessor.GetNamespace() == name.Namespace {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
return true, nil
|
|
case p.bindingGVK:
|
|
for _, hook := range p.Source.Hooks() {
|
|
for _, binding := range hook.Bindings {
|
|
accessor := srce.newBindingAccessor(binding)
|
|
if accessor.GetName() == name.Name && accessor.GetNamespace() == name.Namespace {
|
|
return false, nil
|
|
}
|
|
}
|
|
}
|
|
return true, nil
|
|
default:
|
|
// Do nothing, params are visible immediately
|
|
// Loop until one of the params is visible via get of the param informer
|
|
informer, scope := p.Source.(*policySource[P, B, E]).getParamInformer(objectGVK)
|
|
if informer == nil {
|
|
return true, nil
|
|
}
|
|
|
|
var lister cache.GenericNamespaceLister = informer.Lister()
|
|
if scope == meta.RESTScopeNamespace {
|
|
lister = informer.Lister().ByNamespace(name.Namespace)
|
|
}
|
|
|
|
_, err = lister.Get(name.Name)
|
|
if err != nil {
|
|
if errors.IsNotFound(err) {
|
|
return true, nil
|
|
}
|
|
return false, err
|
|
}
|
|
return false, nil
|
|
}
|
|
})
|
|
}
|
|
|
|
func (p *PolicyTestContext[P, B, E]) updateOne(object runtime.Object) error {
|
|
objectMeta, err := meta.Accessor(object)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
objectMeta.SetResourceVersion(string(uuid.NewUUID()))
|
|
objectGVK, gvr, err := p.inferGVK(object)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch objectGVK {
|
|
case p.policyGVK:
|
|
err := p.policyAndBindingTracker.Update(p.policyGVR, object, objectMeta.GetNamespace())
|
|
if errors.IsNotFound(err) {
|
|
err = p.policyAndBindingTracker.Create(p.policyGVR, object, objectMeta.GetNamespace())
|
|
}
|
|
|
|
return err
|
|
case p.bindingGVK:
|
|
err := p.policyAndBindingTracker.Update(p.bindingGVR, object, objectMeta.GetNamespace())
|
|
if errors.IsNotFound(err) {
|
|
err = p.policyAndBindingTracker.Create(p.bindingGVR, object, objectMeta.GetNamespace())
|
|
}
|
|
|
|
return err
|
|
default:
|
|
if _, ok := object.(*unstructured.Unstructured); ok {
|
|
if err := p.unstructuredTracker.Create(gvr, object, objectMeta.GetNamespace()); err != nil {
|
|
if errors.IsAlreadyExists(err) {
|
|
return p.unstructuredTracker.Update(gvr, object, objectMeta.GetNamespace())
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
} else if err := p.nativeTracker.Create(gvr, object, objectMeta.GetNamespace()); err != nil {
|
|
if errors.IsAlreadyExists(err) {
|
|
return p.nativeTracker.Update(gvr, object, objectMeta.GetNamespace())
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Depending upon object type, waits afterward until the object is synced
|
|
// by the policy source
|
|
func (p *PolicyTestContext[P, B, E]) DeleteAndWait(object ...runtime.Object) error {
|
|
for _, object := range object {
|
|
if err := p.deleteOne(object); err != nil && !errors.IsNotFound(err) {
|
|
return err
|
|
}
|
|
}
|
|
|
|
timeoutCtx, timeoutCancel := context.WithTimeout(p, 3*time.Second)
|
|
defer timeoutCancel()
|
|
|
|
for _, object := range object {
|
|
accessor, err := meta.Accessor(object)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
objectGVK, _, err := p.inferGVK(object)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := p.waitForDelete(
|
|
timeoutCtx,
|
|
objectGVK,
|
|
types.NamespacedName{Name: accessor.GetName(), Namespace: accessor.GetNamespace()}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *PolicyTestContext[P, B, E]) deleteOne(object runtime.Object) error {
|
|
objectMeta, err := meta.Accessor(object)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
objectMeta.SetResourceVersion(string(uuid.NewUUID()))
|
|
objectGVK, gvr, err := p.inferGVK(object)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch objectGVK {
|
|
case p.policyGVK:
|
|
return p.policyAndBindingTracker.Delete(p.policyGVR, objectMeta.GetNamespace(), objectMeta.GetName())
|
|
case p.bindingGVK:
|
|
return p.policyAndBindingTracker.Delete(p.bindingGVR, objectMeta.GetNamespace(), objectMeta.GetName())
|
|
default:
|
|
if _, ok := object.(*unstructured.Unstructured); ok {
|
|
return p.unstructuredTracker.Delete(gvr, objectMeta.GetNamespace(), objectMeta.GetName())
|
|
}
|
|
return p.nativeTracker.Delete(gvr, objectMeta.GetNamespace(), objectMeta.GetName())
|
|
}
|
|
}
|
|
|
|
func (p *PolicyTestContext[P, B, E]) Dispatch(
|
|
new, old runtime.Object,
|
|
operation admission.Operation,
|
|
) error {
|
|
if old == nil && new == nil {
|
|
return fmt.Errorf("both old and new objects cannot be nil")
|
|
}
|
|
|
|
nonNilObject := new
|
|
if nonNilObject == nil {
|
|
nonNilObject = old
|
|
}
|
|
|
|
gvk, gvr, err := p.inferGVK(nonNilObject)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
nonNilMeta, err := meta.Accessor(nonNilObject)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return p.Plugin.Dispatch(
|
|
p,
|
|
admission.NewAttributesRecord(
|
|
new,
|
|
old,
|
|
gvk,
|
|
nonNilMeta.GetName(),
|
|
nonNilMeta.GetNamespace(),
|
|
gvr,
|
|
"",
|
|
operation,
|
|
nil,
|
|
false,
|
|
nil,
|
|
), admission.NewObjectInterfacesFromScheme(p.scheme))
|
|
}
|
|
|
|
func (p *PolicyTestContext[P, B, E]) inferGVK(object runtime.Object) (schema.GroupVersionKind, schema.GroupVersionResource, error) {
|
|
objectGVK := object.GetObjectKind().GroupVersionKind()
|
|
if objectGVK.Empty() {
|
|
// If the object doesn't have a GVK, ask the schema for preferred GVK
|
|
knownKinds, _, err := p.scheme.ObjectKinds(object)
|
|
if err != nil {
|
|
return schema.GroupVersionKind{}, schema.GroupVersionResource{}, err
|
|
} else if len(knownKinds) == 0 {
|
|
return schema.GroupVersionKind{}, schema.GroupVersionResource{}, fmt.Errorf("no known GVKs for object in schema: %T", object)
|
|
}
|
|
toTake := 0
|
|
|
|
// Prefer GVK if it is our fake policy or binding
|
|
for i, knownKind := range knownKinds {
|
|
if knownKind == p.policyGVK || knownKind == p.bindingGVK {
|
|
toTake = i
|
|
break
|
|
}
|
|
}
|
|
|
|
objectGVK = knownKinds[toTake]
|
|
}
|
|
|
|
// Make sure GVK is known to the fake rest mapper. To prevent cryptic error
|
|
mapping, err := p.restMapper.RESTMapping(objectGVK.GroupKind(), objectGVK.Version)
|
|
if err != nil {
|
|
return schema.GroupVersionKind{}, schema.GroupVersionResource{}, err
|
|
}
|
|
return objectGVK, mapping.Resource, nil
|
|
}
|
|
|
|
type FakeList[T runtime.Object] struct {
|
|
metav1.TypeMeta
|
|
metav1.ListMeta
|
|
Items []T
|
|
}
|
|
|
|
func (fl *FakeList[P]) DeepCopyObject() runtime.Object {
|
|
copiedItems := make([]P, len(fl.Items))
|
|
for i, item := range fl.Items {
|
|
copiedItems[i] = item.DeepCopyObject().(P)
|
|
}
|
|
return &FakeList[P]{
|
|
TypeMeta: fl.TypeMeta,
|
|
ListMeta: fl.ListMeta,
|
|
Items: copiedItems,
|
|
}
|
|
}
|
|
|
|
type fakeAuthorizer struct{}
|
|
|
|
func (f fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
|
|
return authorizer.DecisionAllow, "", nil
|
|
}
|