@@ -15,11 +15,16 @@ import (
1515 "helm.sh/helm/v3/pkg/chart"
1616 "helm.sh/helm/v3/pkg/release"
1717 "helm.sh/helm/v3/pkg/storage/driver"
18+ authenticationv1 "k8s.io/api/authentication/v1"
19+ corev1 "k8s.io/api/core/v1"
1820 "k8s.io/apimachinery/pkg/api/equality"
1921 apimeta "k8s.io/apimachinery/pkg/api/meta"
2022 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+ "k8s.io/apimachinery/pkg/runtime"
2124 "k8s.io/apimachinery/pkg/types"
2225 "k8s.io/apimachinery/pkg/util/rand"
26+ "k8s.io/client-go/kubernetes/fake"
27+ clientgotesting "k8s.io/client-go/testing"
2328 ctrl "sigs.k8s.io/controller-runtime"
2429 "sigs.k8s.io/controller-runtime/pkg/client"
2530 crfinalizer "sigs.k8s.io/controller-runtime/pkg/finalizer"
@@ -767,60 +772,205 @@ func TestClusterExtensionBoxcutterApplierFailsDoesNotLeakDeprecationErrors(t *te
767772 require .NoError (t , cl .DeleteAllOf (ctx , & ocv1.ClusterExtension {}))
768773}
769774
770- func TestClusterExtensionServiceAccountNotFound (t * testing.T ) {
771- cl , reconciler := newClientAndReconciler (t , func (d * deps ) {
772- d .RevisionStatesGetter = & MockRevisionStatesGetter {
773- Err : & authentication.ServiceAccountNotFoundError {
774- ServiceAccountName : "missing-sa" ,
775- ServiceAccountNamespace : "default" ,
776- }}
777- })
778-
779- ctx := context .Background ()
780- extKey := types.NamespacedName {Name : fmt .Sprintf ("cluster-extension-test-%s" , rand .String (8 ))}
781-
782- t .Log ("Given a cluster extension with a missing service account" )
783- clusterExtension := & ocv1.ClusterExtension {
784- ObjectMeta : metav1.ObjectMeta {Name : extKey .Name },
785- Spec : ocv1.ClusterExtensionSpec {
786- Source : ocv1.SourceConfig {
787- SourceType : "Catalog" ,
788- Catalog : & ocv1.CatalogFilter {
789- PackageName : "test-package" ,
775+ func TestValidateClusterExtension (t * testing.T ) {
776+ tests := []struct {
777+ name string
778+ validators []controllers.ClusterExtensionValidator
779+ expectError bool
780+ errorMessageIncludes string
781+ }{
782+ {
783+ name : "all validators pass" ,
784+ validators : []controllers.ClusterExtensionValidator {
785+ // Validator that always passes
786+ func (_ context.Context , _ * ocv1.ClusterExtension ) error {
787+ return nil
790788 },
791789 },
792- Namespace : "default" ,
793- ServiceAccount : ocv1.ServiceAccountReference {
794- Name : "missing-sa" ,
790+ expectError : false ,
791+ },
792+ {
793+ name : "validator fails - sets Progressing condition" ,
794+ validators : []controllers.ClusterExtensionValidator {
795+ func (_ context.Context , _ * ocv1.ClusterExtension ) error {
796+ return errors .New ("generic validation error" )
797+ },
798+ },
799+ expectError : true ,
800+ errorMessageIncludes : "generic validation error" ,
801+ },
802+ {
803+ name : "multiple validators - collects all failures" ,
804+ validators : []controllers.ClusterExtensionValidator {
805+ // First validator fails
806+ func (_ context.Context , _ * ocv1.ClusterExtension ) error {
807+ return errors .New ("first validator failed" )
808+ },
809+ // Second validator also fails
810+ func (_ context.Context , _ * ocv1.ClusterExtension ) error {
811+ return errors .New ("second validator failed" )
812+ },
813+ // Third validator fails too
814+ func (_ context.Context , _ * ocv1.ClusterExtension ) error {
815+ return errors .New ("third validator failed" )
816+ },
817+ },
818+ expectError : true ,
819+ errorMessageIncludes : "first validator failed\n second validator failed\n third validator failed" ,
820+ },
821+ {
822+ name : "multiple validators - all pass" ,
823+ validators : []controllers.ClusterExtensionValidator {
824+ func (_ context.Context , _ * ocv1.ClusterExtension ) error {
825+ return nil
826+ },
827+ func (_ context.Context , _ * ocv1.ClusterExtension ) error {
828+ return nil
829+ },
830+ func (_ context.Context , _ * ocv1.ClusterExtension ) error {
831+ return nil
832+ },
833+ },
834+ expectError : false ,
835+ },
836+ {
837+ name : "multiple validators - some pass, some fail" ,
838+ validators : []controllers.ClusterExtensionValidator {
839+ // First validator passes
840+ func (_ context.Context , _ * ocv1.ClusterExtension ) error {
841+ return nil
842+ },
843+ // Second validator fails
844+ func (_ context.Context , _ * ocv1.ClusterExtension ) error {
845+ return errors .New ("validation error 1" )
846+ },
847+ // Third validator passes
848+ func (_ context.Context , _ * ocv1.ClusterExtension ) error {
849+ return nil
850+ },
851+ // Fourth validator fails
852+ func (_ context.Context , _ * ocv1.ClusterExtension ) error {
853+ return errors .New ("validation error 2" )
854+ },
855+ },
856+ expectError : true ,
857+ errorMessageIncludes : "validation error 1\n validation error 2" ,
858+ },
859+ {
860+ name : "service account not found" ,
861+ validators : []controllers.ClusterExtensionValidator {
862+ serviceAccountValidatorWithFakeClient (& corev1.ServiceAccount {
863+ ObjectMeta : metav1.ObjectMeta {
864+ Name : "missing-sa" ,
865+ Namespace : "test-ns" ,
866+ },
867+ }),
868+ },
869+ expectError : true ,
870+ errorMessageIncludes : `service account "test-sa" not found in namespace "test-ns"` ,
871+ },
872+ {
873+ name : "service account found" ,
874+ validators : []controllers.ClusterExtensionValidator {
875+ serviceAccountValidatorWithFakeClient (& corev1.ServiceAccount {
876+ ObjectMeta : metav1.ObjectMeta {
877+ Name : "test-sa" ,
878+ Namespace : "test-ns" ,
879+ },
880+ }),
795881 },
882+ expectError : false ,
796883 },
797884 }
798885
799- require .NoError (t , cl .Create (ctx , clusterExtension ))
886+ for _ , tt := range tests {
887+ t .Run (tt .name , func (t * testing.T ) {
888+ ctx := context .Background ()
800889
801- t .Log ("When reconciling the cluster extension" )
802- res , err := reconciler .Reconcile (ctx , ctrl.Request {NamespacedName : extKey })
890+ cl , reconciler := newClientAndReconciler (t , func (d * deps ) {
891+ d .RevisionStatesGetter = & MockRevisionStatesGetter {
892+ RevisionStates : & controllers.RevisionStates {},
893+ }
894+ d .Validators = tt .validators
895+ })
803896
804- require .Equal (t , ctrl.Result {}, res )
805- require .Error (t , err )
806- var saErr * authentication.ServiceAccountNotFoundError
807- require .ErrorAs (t , err , & saErr )
808- t .Log ("By fetching updated cluster extension after reconcile" )
809- require .NoError (t , cl .Get (ctx , extKey , clusterExtension ))
897+ extKey := types.NamespacedName {Name : fmt .Sprintf ("cluster-extension-test-%s" , rand .String (8 ))}
810898
811- t .Log ("By checking the status conditions" )
812- installedCond := apimeta .FindStatusCondition (clusterExtension .Status .Conditions , ocv1 .TypeInstalled )
813- require .NotNil (t , installedCond )
814- require .Equal (t , metav1 .ConditionUnknown , installedCond .Status )
815- require .Contains (t , installedCond .Message , fmt .Sprintf ("service account %q not found in namespace %q: unable to authenticate with the Kubernetes cluster." ,
816- "missing-sa" , "default" ))
899+ clusterExtension := & ocv1.ClusterExtension {
900+ ObjectMeta : metav1.ObjectMeta {Name : extKey .Name },
901+ Spec : ocv1.ClusterExtensionSpec {
902+ Source : ocv1.SourceConfig {
903+ SourceType : "Catalog" ,
904+ Catalog : & ocv1.CatalogFilter {
905+ PackageName : "test-package" ,
906+ },
907+ },
908+ Namespace : "test-ns" ,
909+ ServiceAccount : ocv1.ServiceAccountReference {
910+ Name : "test-sa" ,
911+ },
912+ },
913+ }
817914
818- progressingCond := apimeta .FindStatusCondition (clusterExtension .Status .Conditions , ocv1 .TypeProgressing )
819- require .NotNil (t , progressingCond )
820- require .Equal (t , metav1 .ConditionTrue , progressingCond .Status )
821- require .Equal (t , ocv1 .ReasonRetrying , progressingCond .Reason )
822- require .Contains (t , progressingCond .Message , "installation cannot proceed due to missing ServiceAccount" )
823- require .NoError (t , cl .DeleteAllOf (ctx , & ocv1.ClusterExtension {}))
915+ require .NoError (t , cl .Create (ctx , clusterExtension ))
916+
917+ t .Log ("When reconciling the cluster extension" )
918+ res , err := reconciler .Reconcile (ctx , ctrl.Request {NamespacedName : extKey })
919+ require .Equal (t , ctrl.Result {}, res )
920+ if tt .expectError {
921+ require .Error (t , err )
922+ t .Log ("By fetching updated cluster extension after reconcile" )
923+ require .NoError (t , cl .Get (ctx , extKey , clusterExtension ))
924+
925+ t .Log ("By checking the status conditions" )
926+ installedCond := apimeta .FindStatusCondition (clusterExtension .Status .Conditions , ocv1 .TypeInstalled )
927+ require .NotNil (t , installedCond )
928+ require .Equal (t , metav1 .ConditionUnknown , installedCond .Status )
929+ require .Contains (t , installedCond .Message , "installation cannot proceed due to the following validation error(s)" )
930+ require .Contains (t , installedCond .Message , tt .errorMessageIncludes )
931+
932+ progressingCond := apimeta .FindStatusCondition (clusterExtension .Status .Conditions , ocv1 .TypeProgressing )
933+ require .NotNil (t , progressingCond )
934+ require .Equal (t , metav1 .ConditionTrue , progressingCond .Status )
935+ require .Equal (t , ocv1 .ReasonRetrying , progressingCond .Reason )
936+ require .Contains (t , progressingCond .Message , "installation cannot proceed due to the following validation error(s)" )
937+ require .Contains (t , progressingCond .Message , tt .errorMessageIncludes )
938+ require .NoError (t , cl .DeleteAllOf (ctx , & ocv1.ClusterExtension {}))
939+ } else {
940+ require .NoError (t , err )
941+ require .NoError (t , cl .Get (ctx , extKey , clusterExtension ))
942+ require .Empty (t , clusterExtension .Status .Conditions )
943+ }
944+ })
945+ }
946+ }
947+
948+ func serviceAccountValidatorWithFakeClient (serviceAccount * corev1.ServiceAccount ) controllers.ClusterExtensionValidator {
949+ if serviceAccount == nil {
950+ return controllers .ServiceAccountValidator (authentication .NewTokenGetter (fake .NewClientset ().CoreV1 ()))
951+ }
952+ fakeClientset := fake .NewClientset (serviceAccount )
953+ fakeClientset .PrependReactor ("create" , "serviceaccounts" , func (action clientgotesting.Action ) (bool , runtime.Object , error ) {
954+ if action .GetSubresource () != "token" || action .GetNamespace () != serviceAccount .Namespace {
955+ // Let other reactors handle standard SA creates
956+ return false , nil , nil
957+ }
958+ createAction , ok := action .(clientgotesting.CreateActionImpl )
959+ if ! ok {
960+ return false , nil , fmt .Errorf ("unexpected action: expected CreateActionImpl but got %#v" , action )
961+ }
962+ if createAction .GetNamespace () != serviceAccount .Namespace || createAction .Name != serviceAccount .Name {
963+ return false , nil , nil
964+ }
965+ tr := & authenticationv1.TokenRequest {
966+ Status : authenticationv1.TokenRequestStatus {
967+ Token : "fake-jwt-token-string" ,
968+ },
969+ }
970+ return true , tr , nil
971+ })
972+ tokenGetter := authentication .NewTokenGetter (fakeClientset .CoreV1 ())
973+ return controllers .ServiceAccountValidator (tokenGetter )
824974}
825975
826976func TestClusterExtensionApplierFailsWithBundleInstalled (t * testing.T ) {
0 commit comments