knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/resolver/authenticatable_resolver_test.go (about)

     1  /*
     2  Copyright 2024 The Knative 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 resolver_test
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"testing"
    23  
    24  	"github.com/google/go-cmp/cmp"
    25  	corev1 "k8s.io/api/core/v1"
    26  	"k8s.io/apimachinery/pkg/runtime"
    27  	"k8s.io/apimachinery/pkg/types"
    28  	duckv1 "knative.dev/pkg/apis/duck/v1"
    29  	fakedynamicclient "knative.dev/pkg/injection/clients/dynamicclient/fake"
    30  	"knative.dev/pkg/ptr"
    31  	"knative.dev/pkg/resolver"
    32  
    33  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    34  	"k8s.io/apimachinery/pkg/runtime/schema"
    35  	"k8s.io/client-go/kubernetes/scheme"
    36  	duckv1alpha1 "knative.dev/pkg/apis/duck/v1alpha1"
    37  	duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1"
    38  	"knative.dev/pkg/client/injection/ducks/duck/v1/authstatus"
    39  	"knative.dev/pkg/tracker"
    40  )
    41  
    42  const (
    43  	authenticatableName       = "testsource"
    44  	authenticatableKind       = "Source"
    45  	authenticatableAPIVersion = "duck.knative.dev/v1"
    46  	authenticatableResource   = "sources.duck.knative.dev"
    47  
    48  	authenticatable2Name       = "test2-source"
    49  	authenticatable2Kind       = "AnotherSource"
    50  	authenticatable2APIVersion = "duck.knative.dev/v1"
    51  	authenticatable2Resource   = "anothersources.duck.knative.dev"
    52  
    53  	unauthenticatableName       = "testunauthenticatable"
    54  	unauthenticatableKind       = "KResource"
    55  	unauthenticatableAPIVersion = "duck.knative.dev/v1alpha1"
    56  
    57  	authenticatableServiceAccountName  = "my-service-account"
    58  	authenticatableServiceAccountName1 = "my-service-account-1"
    59  	authenticatableServiceAccountName2 = "my-service-account-2"
    60  	authenticatable2ServiceAccountName = "service-account-of-2nd-authenticatable"
    61  )
    62  
    63  func init() {
    64  	// Add types to scheme
    65  	duckv1alpha1.AddToScheme(scheme.Scheme)
    66  	duckv1beta1.AddToScheme(scheme.Scheme)
    67  
    68  	scheme.Scheme.AddKnownTypeWithName(
    69  		schema.FromAPIVersionAndKind(unauthenticatableAPIVersion, unauthenticatableKind),
    70  		&unstructured.Unstructured{},
    71  	)
    72  	scheme.Scheme.AddKnownTypeWithName(
    73  		schema.FromAPIVersionAndKind(unauthenticatableAPIVersion, unauthenticatableKind+"List"),
    74  		&unstructured.UnstructuredList{},
    75  	)
    76  	scheme.Scheme.AddKnownTypeWithName(
    77  		schema.FromAPIVersionAndKind(authenticatableAPIVersion, authenticatableKind),
    78  		&unstructured.Unstructured{},
    79  	)
    80  	scheme.Scheme.AddKnownTypeWithName(
    81  		schema.FromAPIVersionAndKind(authenticatableAPIVersion, authenticatableKind+"List"),
    82  		&unstructured.UnstructuredList{},
    83  	)
    84  }
    85  
    86  func TestAuthenticatableResolver_AuthStatusFromObjectReference(t *testing.T) {
    87  	tests := []struct {
    88  		name      string
    89  		objects   []runtime.Object
    90  		objectRef *corev1.ObjectReference
    91  		want      *duckv1.AuthStatus
    92  		wantErr   string
    93  	}{
    94  		{
    95  			name:    "nil everything",
    96  			wantErr: "ref is nil",
    97  		}, {
    98  			name: "Valid authenticatable",
    99  			objects: []runtime.Object{
   100  				getAuthenticatable(),
   101  			},
   102  			objectRef: authenticatableRef(),
   103  			want: &duckv1.AuthStatus{
   104  				ServiceAccountName: ptr.String(authenticatableServiceAccountName),
   105  			},
   106  		}, {
   107  			name: "Valid authenticatable in multiple objects",
   108  			objects: []runtime.Object{
   109  				getUnauthenticatable(),
   110  				getAuthenticatable(),
   111  				getAuthenticatable2(),
   112  			},
   113  			objectRef: authenticatable2Ref(),
   114  			want: &duckv1.AuthStatus{
   115  				ServiceAccountName: ptr.String(authenticatable2ServiceAccountName),
   116  			},
   117  		}, {
   118  			name: "Valid authenticatable multiple SAs",
   119  			objects: []runtime.Object{
   120  				getAuthenticatableWithMultipleSAs(),
   121  			},
   122  			objectRef: authenticatableRef(),
   123  			want: &duckv1.AuthStatus{
   124  				ServiceAccountNames: []string{
   125  					authenticatableServiceAccountName1,
   126  					authenticatableServiceAccountName2,
   127  				},
   128  			},
   129  		}, {
   130  			name: "Unauthenticatable",
   131  			objects: []runtime.Object{
   132  				getUnauthenticatable(),
   133  			},
   134  			objectRef: unauthenticatableRef(),
   135  			wantErr:   fmt.Sprintf(".status.auth is missing in object %s/%s", testNS, unauthenticatableName),
   136  		}, {
   137  			name: "Authenticatable not found",
   138  			objects: []runtime.Object{
   139  				getUnauthenticatable(),
   140  			},
   141  			objectRef: authenticatableRef(),
   142  			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),
   143  		},
   144  	}
   145  	for _, tt := range tests {
   146  		t.Run(tt.name, func(t *testing.T) {
   147  			ctx, _ := fakedynamicclient.With(context.Background(), scheme.Scheme, tt.objects...)
   148  			ctx = authstatus.WithDuck(ctx)
   149  			r := resolver.NewAuthenticatableResolverFromTracker(ctx, tracker.New(func(types.NamespacedName) {}, 0))
   150  
   151  			// Run it twice since this should be idempotent. AuthenticatableResolver should
   152  			// not modify the cache's copy.
   153  			_, _ = r.AuthStatusFromObjectReference(tt.objectRef, getAuthenticatable())
   154  			authStatus, gotErr := r.AuthStatusFromObjectReference(tt.objectRef, getAuthenticatable())
   155  
   156  			if gotErr != nil {
   157  				if tt.wantErr != "" {
   158  					if got, want := gotErr.Error(), tt.wantErr; got != want {
   159  						t.Errorf("Unexpected error (-want, +got) =\n%s", cmp.Diff(want, got))
   160  					}
   161  				} else {
   162  					t.Error("Unexpected error:", gotErr)
   163  				}
   164  				return
   165  			}
   166  
   167  			if got, want := authStatus, tt.want; !cmp.Equal(got, want) {
   168  				t.Errorf("Unexpected object (-want, +got) =\n%s", cmp.Diff(got, want))
   169  			}
   170  		})
   171  	}
   172  }
   173  
   174  func getAuthenticatable() *unstructured.Unstructured {
   175  	return &unstructured.Unstructured{
   176  		Object: map[string]interface{}{
   177  			"apiVersion": authenticatableAPIVersion,
   178  			"kind":       authenticatableKind,
   179  			"metadata": map[string]interface{}{
   180  				"namespace": testNS,
   181  				"name":      authenticatableName,
   182  			},
   183  			"status": map[string]interface{}{
   184  				"auth": map[string]interface{}{
   185  					"serviceAccountName": authenticatableServiceAccountName,
   186  				},
   187  			},
   188  		},
   189  	}
   190  }
   191  
   192  func getAuthenticatable2() *unstructured.Unstructured {
   193  	return &unstructured.Unstructured{
   194  		Object: map[string]interface{}{
   195  			"apiVersion": authenticatable2APIVersion,
   196  			"kind":       authenticatable2Kind,
   197  			"metadata": map[string]interface{}{
   198  				"namespace": testNS,
   199  				"name":      authenticatable2Name,
   200  			},
   201  			"status": map[string]interface{}{
   202  				"auth": map[string]interface{}{
   203  					"serviceAccountName": authenticatable2ServiceAccountName,
   204  				},
   205  			},
   206  		},
   207  	}
   208  }
   209  
   210  func getAuthenticatableWithMultipleSAs() *unstructured.Unstructured {
   211  	return &unstructured.Unstructured{
   212  		Object: map[string]interface{}{
   213  			"apiVersion": authenticatableAPIVersion,
   214  			"kind":       authenticatableKind,
   215  			"metadata": map[string]interface{}{
   216  				"namespace": testNS,
   217  				"name":      authenticatableName,
   218  			},
   219  			"status": map[string]interface{}{
   220  				"auth": map[string]interface{}{
   221  					"serviceAccountNames": []interface{}{
   222  						authenticatableServiceAccountName1,
   223  						authenticatableServiceAccountName2,
   224  					},
   225  				},
   226  			},
   227  		},
   228  	}
   229  }
   230  
   231  func getUnauthenticatable() *unstructured.Unstructured {
   232  	return &unstructured.Unstructured{
   233  		Object: map[string]interface{}{
   234  			"apiVersion": unauthenticatableAPIVersion,
   235  			"kind":       unauthenticatableKind,
   236  			"metadata": map[string]interface{}{
   237  				"namespace": testNS,
   238  				"name":      unauthenticatableName,
   239  			},
   240  			"status": map[string]interface{}{
   241  				"something": map[string]interface{}{
   242  					"foo": "bar",
   243  				},
   244  			},
   245  		},
   246  	}
   247  }
   248  
   249  func authenticatableRef() *corev1.ObjectReference {
   250  	return &corev1.ObjectReference{
   251  		Kind:       authenticatableKind,
   252  		Name:       authenticatableName,
   253  		APIVersion: authenticatableAPIVersion,
   254  		Namespace:  testNS,
   255  	}
   256  }
   257  
   258  func authenticatable2Ref() *corev1.ObjectReference {
   259  	return &corev1.ObjectReference{
   260  		Kind:       authenticatable2Kind,
   261  		Name:       authenticatable2Name,
   262  		APIVersion: authenticatable2APIVersion,
   263  		Namespace:  testNS,
   264  	}
   265  }
   266  
   267  func unauthenticatableRef() *corev1.ObjectReference {
   268  	return &corev1.ObjectReference{
   269  		Kind:       unauthenticatableKind,
   270  		Name:       unauthenticatableName,
   271  		APIVersion: unauthenticatableAPIVersion,
   272  		Namespace:  testNS,
   273  	}
   274  }