@@ -15,11 +15,14 @@ import (
1515 "helm.sh/helm/v3/pkg/chart"
1616 "helm.sh/helm/v3/pkg/release"
1717 "helm.sh/helm/v3/pkg/storage/driver"
18+ corev1 "k8s.io/api/core/v1"
1819 "k8s.io/apimachinery/pkg/api/equality"
1920 apimeta "k8s.io/apimachinery/pkg/api/meta"
2021 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22+ "k8s.io/apimachinery/pkg/runtime"
2123 "k8s.io/apimachinery/pkg/types"
2224 "k8s.io/apimachinery/pkg/util/rand"
25+ "k8s.io/client-go/kubernetes/fake"
2326 ctrl "sigs.k8s.io/controller-runtime"
2427 "sigs.k8s.io/controller-runtime/pkg/client"
2528 crfinalizer "sigs.k8s.io/controller-runtime/pkg/finalizer"
@@ -768,14 +771,26 @@ func TestClusterExtensionBoxcutterApplierFailsDoesNotLeakDeprecationErrors(t *te
768771}
769772
770773func TestClusterExtensionServiceAccountNotFound (t * testing.T ) {
774+ // Create fake Kubernetes clientset (without creating the service account)
775+ fakeClientset := fake .NewClientset ()
776+
777+ // Create concrete TokenGetter with the fake client
778+ tokenGetter := authentication .NewTokenGetter (fakeClientset .CoreV1 ())
779+
771780 cl , reconciler := newClientAndReconciler (t , func (d * deps ) {
772781 d .RevisionStatesGetter = & MockRevisionStatesGetter {
773- Err : & authentication.ServiceAccountNotFoundError {
774- ServiceAccountName : "missing-sa" ,
775- ServiceAccountNamespace : "default" ,
776- }}
782+ RevisionStates : & controllers.RevisionStates {},
783+ }
777784 })
778785
786+ // Add validation step to the beginning of the reconcile steps
787+ reconciler .ReconcileSteps = append (
788+ []controllers.ReconcileStepFunc {controllers .ValidateClusterExtension (
789+ controllers .ServiceAccountValidator (tokenGetter ),
790+ )},
791+ reconciler .ReconcileSteps ... ,
792+ )
793+
779794 ctx := context .Background ()
780795 extKey := types.NamespacedName {Name : fmt .Sprintf ("cluster-extension-test-%s" , rand .String (8 ))}
781796
@@ -803,26 +818,197 @@ func TestClusterExtensionServiceAccountNotFound(t *testing.T) {
803818
804819 require .Equal (t , ctrl.Result {}, res )
805820 require .Error (t , err )
806- var saErr * authentication.ServiceAccountNotFoundError
807- require .ErrorAs (t , err , & saErr )
808821 t .Log ("By fetching updated cluster extension after reconcile" )
809822 require .NoError (t , cl .Get (ctx , extKey , clusterExtension ))
810823
811824 t .Log ("By checking the status conditions" )
812825 installedCond := apimeta .FindStatusCondition (clusterExtension .Status .Conditions , ocv1 .TypeInstalled )
813826 require .NotNil (t , installedCond )
814827 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" ) )
828+ require .Contains (t , installedCond .Message , "installation cannot proceed due to the following validation error(s)" )
829+ require . Contains ( t , installedCond . Message , "service account \" missing-sa\" not found" )
817830
818831 progressingCond := apimeta .FindStatusCondition (clusterExtension .Status .Conditions , ocv1 .TypeProgressing )
819832 require .NotNil (t , progressingCond )
820833 require .Equal (t , metav1 .ConditionTrue , progressingCond .Status )
821834 require .Equal (t , ocv1 .ReasonRetrying , progressingCond .Reason )
822- require .Contains (t , progressingCond .Message , "installation cannot proceed due to missing ServiceAccount" )
835+ require .Contains (t , progressingCond .Message , "installation cannot proceed due to the following validation error(s)" )
836+ require .Contains (t , progressingCond .Message , "service account \" missing-sa\" not found" )
823837 require .NoError (t , cl .DeleteAllOf (ctx , & ocv1.ClusterExtension {}))
824838}
825839
840+ func TestValidateClusterExtension (t * testing.T ) {
841+ tests := []struct {
842+ name string
843+ validators []controllers.ClusterExtensionValidator
844+ expectError bool
845+ errorMessageIncludes string
846+ }{
847+ {
848+ name : "all validators pass" ,
849+ validators : []controllers.ClusterExtensionValidator {
850+ // Validator that always passes
851+ func (_ context.Context , _ * ocv1.ClusterExtension ) error {
852+ return nil
853+ },
854+ },
855+ expectError : false ,
856+ },
857+ {
858+ name : "validator fails - sets Progressing condition" ,
859+ validators : []controllers.ClusterExtensionValidator {
860+ func (_ context.Context , _ * ocv1.ClusterExtension ) error {
861+ return errors .New ("generic validation error" )
862+ },
863+ },
864+ expectError : true ,
865+ errorMessageIncludes : "generic validation error" ,
866+ },
867+ {
868+ name : "multiple validators - collects all failures" ,
869+ validators : []controllers.ClusterExtensionValidator {
870+ // First validator fails
871+ func (_ context.Context , _ * ocv1.ClusterExtension ) error {
872+ return errors .New ("first validator failed" )
873+ },
874+ // Second validator also fails
875+ func (_ context.Context , _ * ocv1.ClusterExtension ) error {
876+ return errors .New ("second validator failed" )
877+ },
878+ // Third validator fails too
879+ func (_ context.Context , _ * ocv1.ClusterExtension ) error {
880+ return errors .New ("third validator failed" )
881+ },
882+ },
883+ expectError : true ,
884+ errorMessageIncludes : "first validator failed\n second validator failed\n third validator failed" ,
885+ },
886+ {
887+ name : "multiple validators - all pass" ,
888+ validators : []controllers.ClusterExtensionValidator {
889+ func (_ context.Context , _ * ocv1.ClusterExtension ) error {
890+ return nil
891+ },
892+ func (_ context.Context , _ * ocv1.ClusterExtension ) error {
893+ return nil
894+ },
895+ func (_ context.Context , _ * ocv1.ClusterExtension ) error {
896+ return nil
897+ },
898+ },
899+ expectError : false ,
900+ },
901+ {
902+ name : "multiple validators - some pass, some fail" ,
903+ validators : []controllers.ClusterExtensionValidator {
904+ // First validator passes
905+ func (_ context.Context , _ * ocv1.ClusterExtension ) error {
906+ return nil
907+ },
908+ // Second validator fails
909+ func (_ context.Context , _ * ocv1.ClusterExtension ) error {
910+ return errors .New ("validation error 1" )
911+ },
912+ // Third validator passes
913+ func (_ context.Context , _ * ocv1.ClusterExtension ) error {
914+ return nil
915+ },
916+ // Fourth validator fails
917+ func (_ context.Context , _ * ocv1.ClusterExtension ) error {
918+ return errors .New ("validation error 2" )
919+ },
920+ },
921+ expectError : true ,
922+ errorMessageIncludes : "validation error 1\n validation error 2" ,
923+ },
924+ {
925+ name : "service account not found" ,
926+ validators : []controllers.ClusterExtensionValidator {
927+ newServiceAccountValidator (),
928+ },
929+ expectError : true ,
930+ errorMessageIncludes : "service account \" missing-sa\" not found" ,
931+ },
932+ {
933+ name : "service account found" ,
934+ validators : []controllers.ClusterExtensionValidator {
935+ newServiceAccountValidator (& corev1.ServiceAccount {
936+ ObjectMeta : metav1.ObjectMeta {
937+ Name : "test-sa" ,
938+ Namespace : "test-namespace" ,
939+ },
940+ }),
941+ },
942+ expectError : false ,
943+ },
944+ }
945+
946+ for _ , tt := range tests {
947+ t .Run (tt .name , func (t * testing.T ) {
948+ ctx := context .Background ()
949+
950+ cl , reconciler := newClientAndReconciler (t , func (d * deps ) {
951+ d .RevisionStatesGetter = & MockRevisionStatesGetter {
952+ RevisionStates : & controllers.RevisionStates {},
953+ }
954+ d .Validators = tt .validators
955+ })
956+
957+ extKey := types.NamespacedName {Name : fmt .Sprintf ("cluster-extension-test-%s" , rand .String (8 ))}
958+
959+ t .Log ("Given a cluster extension with a missing service account" )
960+ clusterExtension := & ocv1.ClusterExtension {
961+ ObjectMeta : metav1.ObjectMeta {Name : extKey .Name },
962+ Spec : ocv1.ClusterExtensionSpec {
963+ Source : ocv1.SourceConfig {
964+ SourceType : "Catalog" ,
965+ Catalog : & ocv1.CatalogFilter {
966+ PackageName : "test-package" ,
967+ },
968+ },
969+ Namespace : "default" ,
970+ ServiceAccount : ocv1.ServiceAccountReference {
971+ Name : "missing-sa" ,
972+ },
973+ },
974+ }
975+
976+ require .NoError (t , cl .Create (ctx , clusterExtension ))
977+
978+ t .Log ("When reconciling the cluster extension" )
979+ res , err := reconciler .Reconcile (ctx , ctrl.Request {NamespacedName : extKey })
980+ require .Equal (t , ctrl.Result {}, res )
981+ if tt .expectError {
982+ require .Error (t , err )
983+ t .Log ("By fetching updated cluster extension after reconcile" )
984+ require .NoError (t , cl .Get (ctx , extKey , clusterExtension ))
985+
986+ t .Log ("By checking the status conditions" )
987+ installedCond := apimeta .FindStatusCondition (clusterExtension .Status .Conditions , ocv1 .TypeInstalled )
988+ require .NotNil (t , installedCond )
989+ require .Equal (t , metav1 .ConditionUnknown , installedCond .Status )
990+ require .Contains (t , installedCond .Message , "installation cannot proceed due to the following validation error(s)" )
991+ require .Contains (t , installedCond .Message , tt .errorMessageIncludes )
992+
993+ progressingCond := apimeta .FindStatusCondition (clusterExtension .Status .Conditions , ocv1 .TypeProgressing )
994+ require .NotNil (t , progressingCond )
995+ require .Equal (t , metav1 .ConditionTrue , progressingCond .Status )
996+ require .Equal (t , ocv1 .ReasonRetrying , progressingCond .Reason )
997+ require .Contains (t , progressingCond .Message , "installation cannot proceed due to the following validation error(s)" )
998+ require .Contains (t , progressingCond .Message , tt .errorMessageIncludes )
999+ require .NoError (t , cl .DeleteAllOf (ctx , & ocv1.ClusterExtension {}))
1000+ }
1001+ })
1002+ }
1003+ }
1004+
1005+ func newServiceAccountValidator (objs ... runtime.Object ) controllers.ClusterExtensionValidator {
1006+ fakeClientset := fake .NewClientset (objs ... )
1007+ tokenGetter := authentication .NewTokenGetter (fakeClientset .CoreV1 ())
1008+
1009+ return controllers .ServiceAccountValidator (tokenGetter )
1010+ }
1011+
8261012func TestClusterExtensionApplierFailsWithBundleInstalled (t * testing.T ) {
8271013 mockApplier := & MockApplier {
8281014 installCompleted : true ,
0 commit comments