From 339c22b8218ccdf0a3e6210e9d1365b81853b594 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20St=C3=A4bler?= Date: Fri, 14 Jun 2024 15:52:39 +0200 Subject: [PATCH] Add AuthenticatableType duck type (#3056) * Add AuthenticatableType * Add Resolver for AuthenticatableType * Run gofmt and goimports * Fix linter issues --- apis/duck/v1/auth_types.go | 93 ++++++ apis/duck/v1/zz_generated.deepcopy.go | 81 ++++++ .../ducks/duck/v1/authstatus/authstatus.go | 60 ++++ .../ducks/duck/v1/authstatus/fake/fake.go | 30 ++ resolver/authenticatable_resolver.go | 117 ++++++++ resolver/authenticatable_resolver_test.go | 274 ++++++++++++++++++ 6 files changed, 655 insertions(+) create mode 100644 client/injection/ducks/duck/v1/authstatus/authstatus.go create mode 100644 client/injection/ducks/duck/v1/authstatus/fake/fake.go create mode 100644 resolver/authenticatable_resolver.go create mode 100644 resolver/authenticatable_resolver_test.go diff --git a/apis/duck/v1/auth_types.go b/apis/duck/v1/auth_types.go index 5d76a7b42..dfb81cbe6 100644 --- a/apis/duck/v1/auth_types.go +++ b/apis/duck/v1/auth_types.go @@ -16,6 +16,21 @@ limitations under the License. package v1 +import ( + "context" + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "knative.dev/pkg/apis" + "knative.dev/pkg/apis/duck/ducktypes" + "knative.dev/pkg/kmeta" + "knative.dev/pkg/ptr" +) + +// +genduck + // AuthStatus is meant to provide the generated service account name // in the resource status. type AuthStatus struct { @@ -28,3 +43,81 @@ type AuthStatus struct { // when the component uses multiple identities (e.g. in case of a Parallel). ServiceAccountNames []string `json:"serviceAccountNames,omitempty"` } + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// AuthenticatableType is a skeleton type wrapping AuthStatus in the manner we expect +// resource writers defining compatible resources to embed it. We will +// typically use this type to deserialize AuthenticatableType ObjectReferences and +// access the AuthenticatableType data. This is not a real resource. +type AuthenticatableType struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Status AuthenticatableStatus `json:"status"` +} + +type AuthenticatableStatus struct { + // Auth contains the service account name for the subscription + // +optional + Auth *AuthStatus `json:"auth,omitempty"` +} + +var ( + // AuthStatus is a Convertible type. + _ apis.Convertible = (*AuthStatus)(nil) + + // Verify AuthenticatableType resources meet duck contracts. + _ apis.Listable = (*AuthenticatableType)(nil) + _ ducktypes.Populatable = (*AuthenticatableType)(nil) + _ kmeta.OwnerRefable = (*AuthenticatableType)(nil) +) + +// GetFullType implements duck.Implementable +func (*AuthStatus) GetFullType() ducktypes.Populatable { + return &AuthenticatableType{} +} + +// ConvertTo implements apis.Convertible +func (a *AuthStatus) ConvertTo(_ context.Context, to apis.Convertible) error { + return fmt.Errorf("v1 is the highest known version, got: %T", to) +} + +// ConvertFrom implements apis.Convertible +func (a *AuthStatus) ConvertFrom(_ context.Context, from apis.Convertible) error { + return fmt.Errorf("v1 is the highest known version, got: %T", from) +} + +// Populate implements duck.Populatable +func (t *AuthenticatableType) Populate() { + t.Status = AuthenticatableStatus{ + Auth: &AuthStatus{ + // Populate ALL fields + ServiceAccountName: ptr.String("foo"), + ServiceAccountNames: []string{ + "bar", + "baz", + }, + }, + } +} + +// GetGroupVersionKind implements kmeta.OwnerRefable +func (t *AuthenticatableType) GetGroupVersionKind() schema.GroupVersionKind { + return t.GroupVersionKind() +} + +// GetListType implements apis.Listable +func (*AuthenticatableType) GetListType() runtime.Object { + return &AuthenticatableTypeList{} +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// AuthenticatableTypeList is a list of AuthenticatableType resources +type AuthenticatableTypeList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []AuthenticatableType `json:"items"` +} diff --git a/apis/duck/v1/zz_generated.deepcopy.go b/apis/duck/v1/zz_generated.deepcopy.go index 9dab1a912..bc263edfd 100644 --- a/apis/duck/v1/zz_generated.deepcopy.go +++ b/apis/duck/v1/zz_generated.deepcopy.go @@ -176,6 +176,87 @@ func (in *AuthStatus) DeepCopy() *AuthStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthenticatableStatus) DeepCopyInto(out *AuthenticatableStatus) { + *out = *in + if in.Auth != nil { + in, out := &in.Auth, &out.Auth + *out = new(AuthStatus) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthenticatableStatus. +func (in *AuthenticatableStatus) DeepCopy() *AuthenticatableStatus { + if in == nil { + return nil + } + out := new(AuthenticatableStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthenticatableType) DeepCopyInto(out *AuthenticatableType) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthenticatableType. +func (in *AuthenticatableType) DeepCopy() *AuthenticatableType { + if in == nil { + return nil + } + out := new(AuthenticatableType) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AuthenticatableType) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthenticatableTypeList) DeepCopyInto(out *AuthenticatableTypeList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]AuthenticatableType, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthenticatableTypeList. +func (in *AuthenticatableTypeList) DeepCopy() *AuthenticatableTypeList { + if in == nil { + return nil + } + out := new(AuthenticatableTypeList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AuthenticatableTypeList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Binding) DeepCopyInto(out *Binding) { *out = *in diff --git a/client/injection/ducks/duck/v1/authstatus/authstatus.go b/client/injection/ducks/duck/v1/authstatus/authstatus.go new file mode 100644 index 000000000..8f2a26501 --- /dev/null +++ b/client/injection/ducks/duck/v1/authstatus/authstatus.go @@ -0,0 +1,60 @@ +/* +Copyright 2022 The Knative 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. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package authstatus + +import ( + context "context" + + duck "knative.dev/pkg/apis/duck" + v1 "knative.dev/pkg/apis/duck/v1" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + dynamicclient "knative.dev/pkg/injection/clients/dynamicclient" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterDuck(WithDuck) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct{} + +func WithDuck(ctx context.Context) context.Context { + dc := dynamicclient.Get(ctx) + dif := &duck.CachedInformerFactory{ + Delegate: &duck.TypedInformerFactory{ + Client: dc, + Type: (&v1.AuthStatus{}).GetFullType(), + ResyncPeriod: controller.GetResyncPeriod(ctx), + StopChannel: ctx.Done(), + }, + } + return context.WithValue(ctx, Key{}, dif) +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) duck.InformerFactory { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch knative.dev/pkg/apis/duck.InformerFactory from context.") + } + return untyped.(duck.InformerFactory) +} diff --git a/client/injection/ducks/duck/v1/authstatus/fake/fake.go b/client/injection/ducks/duck/v1/authstatus/fake/fake.go new file mode 100644 index 000000000..47111b089 --- /dev/null +++ b/client/injection/ducks/duck/v1/authstatus/fake/fake.go @@ -0,0 +1,30 @@ +/* +Copyright 2022 The Knative 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. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package fake + +import ( + authstatus "knative.dev/pkg/client/injection/ducks/duck/v1/authstatus" + injection "knative.dev/pkg/injection" +) + +var Get = authstatus.Get + +func init() { + injection.Fake.RegisterDuck(authstatus.WithDuck) +} diff --git a/resolver/authenticatable_resolver.go b/resolver/authenticatable_resolver.go new file mode 100644 index 000000000..78dd69bdd --- /dev/null +++ b/resolver/authenticatable_resolver.go @@ -0,0 +1,117 @@ +/* +Copyright 2024 The Knative 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 resolver + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/tools/cache" + + corev1 "k8s.io/api/core/v1" + apierrs "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + "knative.dev/pkg/client/injection/ducks/duck/v1/authstatus" + "knative.dev/pkg/controller" + + pkgapisduck "knative.dev/pkg/apis/duck" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/tracker" +) + +// AuthenticatableResolver resolves ObjectReferences into a AuthenticatableType. +type AuthenticatableResolver struct { + tracker tracker.Interface + listerFactory func(schema.GroupVersionResource) (cache.GenericLister, error) +} + +// NewAuthenticatableResolverFromTracker constructs a new AuthenticatableResolver with context and a tracker. +func NewAuthenticatableResolverFromTracker(ctx context.Context, t tracker.Interface) *AuthenticatableResolver { + ret := &AuthenticatableResolver{ + tracker: t, + } + + informerFactory := &pkgapisduck.CachedInformerFactory{ + Delegate: &pkgapisduck.EnqueueInformerFactory{ + Delegate: authstatus.Get(ctx), + EventHandler: controller.HandleAll(ret.tracker.OnChanged), + }, + } + + ret.listerFactory = func(gvr schema.GroupVersionResource) (cache.GenericLister, error) { + _, l, err := informerFactory.Get(ctx, gvr) + return l, err + } + + return ret +} + +// AuthStatusFromObjectReference returns the AuthStatus from an object +func (r *AuthenticatableResolver) AuthStatusFromObjectReference(ref *corev1.ObjectReference, parent interface{}) (*duckv1.AuthStatus, error) { + if ref == nil { + return nil, apierrs.NewBadRequest("ref is nil") + } + + authenticatable, err := r.authenticatableFromObjectReference(ref, parent) + if err != nil { + return nil, fmt.Errorf("failed to get authenticatable %s/%s: %w", ref.Namespace, ref.Name, err) + } + + if authenticatable.Status.Auth == nil { + return nil, fmt.Errorf(".status.auth is missing in object %s/%s", ref.Namespace, ref.Name) + } + + return authenticatable.Status.Auth, nil +} + +// authenticatableFromObjectReference resolves an object reference into an AuthenticatableType +func (r *AuthenticatableResolver) authenticatableFromObjectReference(ref *corev1.ObjectReference, parent interface{}) (*duckv1.AuthenticatableType, error) { + if ref == nil { + return nil, apierrs.NewBadRequest("ref is nil") + } + + gvr, _ := meta.UnsafeGuessKindToResource(ref.GroupVersionKind()) + if err := r.tracker.TrackReference(tracker.Reference{ + APIVersion: ref.APIVersion, + Kind: ref.Kind, + Namespace: ref.Namespace, + Name: ref.Name, + }, parent); err != nil { + return nil, fmt.Errorf("failed to track reference %s %s/%s: %w", gvr.String(), ref.Namespace, ref.Name, err) + } + + lister, err := r.listerFactory(gvr) + if err != nil { + return nil, fmt.Errorf("failed to get lister for %s: %w", gvr.String(), err) + } + + obj, err := lister.ByNamespace(ref.Namespace).Get(ref.Name) + if err != nil { + return nil, fmt.Errorf("failed to get object %s/%s: %w", ref.Namespace, ref.Name, err) + } + + authenticatable, ok := obj.(*duckv1.AuthenticatableType) + if !ok { + return nil, apierrs.NewBadRequest(fmt.Sprintf("%s(%T) is not an AuthenticatableType", ref, ref)) + } + + // Do not modify informer copy. + authenticatable = authenticatable.DeepCopy() + + return authenticatable, nil +} diff --git a/resolver/authenticatable_resolver_test.go b/resolver/authenticatable_resolver_test.go new file mode 100644 index 000000000..608912092 --- /dev/null +++ b/resolver/authenticatable_resolver_test.go @@ -0,0 +1,274 @@ +/* +Copyright 2024 The Knative 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 resolver_test + +import ( + "context" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + duckv1 "knative.dev/pkg/apis/duck/v1" + fakedynamicclient "knative.dev/pkg/injection/clients/dynamicclient/fake" + "knative.dev/pkg/ptr" + "knative.dev/pkg/resolver" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/kubernetes/scheme" + duckv1alpha1 "knative.dev/pkg/apis/duck/v1alpha1" + duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1" + "knative.dev/pkg/client/injection/ducks/duck/v1/authstatus" + "knative.dev/pkg/tracker" +) + +const ( + authenticatableName = "testsource" + authenticatableKind = "Source" + authenticatableAPIVersion = "duck.knative.dev/v1" + authenticatableResource = "sources.duck.knative.dev" + + authenticatable2Name = "test2-source" + authenticatable2Kind = "AnotherSource" + authenticatable2APIVersion = "duck.knative.dev/v1" + authenticatable2Resource = "anothersources.duck.knative.dev" + + unauthenticatableName = "testunauthenticatable" + unauthenticatableKind = "KResource" + unauthenticatableAPIVersion = "duck.knative.dev/v1alpha1" + + authenticatableServiceAccountName = "my-service-account" + authenticatableServiceAccountName1 = "my-service-account-1" + authenticatableServiceAccountName2 = "my-service-account-2" + authenticatable2ServiceAccountName = "service-account-of-2nd-authenticatable" +) + +func init() { + // Add types to scheme + duckv1alpha1.AddToScheme(scheme.Scheme) + duckv1beta1.AddToScheme(scheme.Scheme) + + scheme.Scheme.AddKnownTypeWithName( + schema.FromAPIVersionAndKind(unauthenticatableAPIVersion, unauthenticatableKind), + &unstructured.Unstructured{}, + ) + scheme.Scheme.AddKnownTypeWithName( + schema.FromAPIVersionAndKind(unauthenticatableAPIVersion, unauthenticatableKind+"List"), + &unstructured.UnstructuredList{}, + ) + scheme.Scheme.AddKnownTypeWithName( + schema.FromAPIVersionAndKind(authenticatableAPIVersion, authenticatableKind), + &unstructured.Unstructured{}, + ) + scheme.Scheme.AddKnownTypeWithName( + schema.FromAPIVersionAndKind(authenticatableAPIVersion, authenticatableKind+"List"), + &unstructured.UnstructuredList{}, + ) +} + +func TestAuthenticatableResolver_AuthStatusFromObjectReference(t *testing.T) { + tests := []struct { + name string + objects []runtime.Object + objectRef *corev1.ObjectReference + want *duckv1.AuthStatus + wantErr string + }{ + { + name: "nil everything", + wantErr: "ref is nil", + }, { + name: "Valid authenticatable", + objects: []runtime.Object{ + getAuthenticatable(), + }, + objectRef: authenticatableRef(), + want: &duckv1.AuthStatus{ + ServiceAccountName: ptr.String(authenticatableServiceAccountName), + }, + }, { + name: "Valid authenticatable in multiple objects", + objects: []runtime.Object{ + getUnauthenticatable(), + getAuthenticatable(), + getAuthenticatable2(), + }, + objectRef: authenticatable2Ref(), + want: &duckv1.AuthStatus{ + ServiceAccountName: ptr.String(authenticatable2ServiceAccountName), + }, + }, { + name: "Valid authenticatable multiple SAs", + objects: []runtime.Object{ + getAuthenticatableWithMultipleSAs(), + }, + objectRef: authenticatableRef(), + want: &duckv1.AuthStatus{ + ServiceAccountNames: []string{ + authenticatableServiceAccountName1, + authenticatableServiceAccountName2, + }, + }, + }, { + name: "Unauthenticatable", + objects: []runtime.Object{ + getUnauthenticatable(), + }, + objectRef: unauthenticatableRef(), + wantErr: fmt.Sprintf(".status.auth is missing in object %s/%s", testNS, unauthenticatableName), + }, { + name: "Authenticatable not found", + objects: []runtime.Object{ + getUnauthenticatable(), + }, + objectRef: authenticatableRef(), + wantErr: fmt.Sprintf("failed to get authenticatable %s/%s: failed to get object %s/%s: %s %q not found", testNS, authenticatableName, testNS, authenticatableName, authenticatableResource, authenticatableName), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, _ := fakedynamicclient.With(context.Background(), scheme.Scheme, tt.objects...) + ctx = authstatus.WithDuck(ctx) + r := resolver.NewAuthenticatableResolverFromTracker(ctx, tracker.New(func(types.NamespacedName) {}, 0)) + + // Run it twice since this should be idempotent. AuthenticatableResolver should + // not modify the cache's copy. + _, _ = r.AuthStatusFromObjectReference(tt.objectRef, getAuthenticatable()) + authStatus, gotErr := r.AuthStatusFromObjectReference(tt.objectRef, getAuthenticatable()) + + if gotErr != nil { + if tt.wantErr != "" { + if got, want := gotErr.Error(), tt.wantErr; got != want { + t.Errorf("Unexpected error (-want, +got) =\n%s", cmp.Diff(want, got)) + } + } else { + t.Error("Unexpected error:", gotErr) + } + return + } + + if got, want := authStatus, tt.want; !cmp.Equal(got, want) { + t.Errorf("Unexpected object (-want, +got) =\n%s", cmp.Diff(got, want)) + } + }) + } +} + +func getAuthenticatable() *unstructured.Unstructured { + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": authenticatableAPIVersion, + "kind": authenticatableKind, + "metadata": map[string]interface{}{ + "namespace": testNS, + "name": authenticatableName, + }, + "status": map[string]interface{}{ + "auth": map[string]interface{}{ + "serviceAccountName": authenticatableServiceAccountName, + }, + }, + }, + } +} + +func getAuthenticatable2() *unstructured.Unstructured { + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": authenticatable2APIVersion, + "kind": authenticatable2Kind, + "metadata": map[string]interface{}{ + "namespace": testNS, + "name": authenticatable2Name, + }, + "status": map[string]interface{}{ + "auth": map[string]interface{}{ + "serviceAccountName": authenticatable2ServiceAccountName, + }, + }, + }, + } +} + +func getAuthenticatableWithMultipleSAs() *unstructured.Unstructured { + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": authenticatableAPIVersion, + "kind": authenticatableKind, + "metadata": map[string]interface{}{ + "namespace": testNS, + "name": authenticatableName, + }, + "status": map[string]interface{}{ + "auth": map[string]interface{}{ + "serviceAccountNames": []interface{}{ + authenticatableServiceAccountName1, + authenticatableServiceAccountName2, + }, + }, + }, + }, + } +} + +func getUnauthenticatable() *unstructured.Unstructured { + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": unauthenticatableAPIVersion, + "kind": unauthenticatableKind, + "metadata": map[string]interface{}{ + "namespace": testNS, + "name": unauthenticatableName, + }, + "status": map[string]interface{}{ + "something": map[string]interface{}{ + "foo": "bar", + }, + }, + }, + } +} + +func authenticatableRef() *corev1.ObjectReference { + return &corev1.ObjectReference{ + Kind: authenticatableKind, + Name: authenticatableName, + APIVersion: authenticatableAPIVersion, + Namespace: testNS, + } +} + +func authenticatable2Ref() *corev1.ObjectReference { + return &corev1.ObjectReference{ + Kind: authenticatable2Kind, + Name: authenticatable2Name, + APIVersion: authenticatable2APIVersion, + Namespace: testNS, + } +} + +func unauthenticatableRef() *corev1.ObjectReference { + return &corev1.ObjectReference{ + Kind: unauthenticatableKind, + Name: unauthenticatableName, + APIVersion: unauthenticatableAPIVersion, + Namespace: testNS, + } +}