diff --git a/backend/eventloop/workspace_resource_event_loop.go b/backend/eventloop/workspace_resource_event_loop.go index bcf76bce29..fce3aa6ae7 100644 --- a/backend/eventloop/workspace_resource_event_loop.go +++ b/backend/eventloop/workspace_resource_event_loop.go @@ -45,6 +45,9 @@ type workspaceResourceLoopMessageType string const ( workspaceResourceLoopMessageType_processRepositoryCredential workspaceResourceLoopMessageType = "processRepositoryCredential" workspaceResourceLoopMessageType_processManagedEnvironment workspaceResourceLoopMessageType = "processManagedEnvironment" + + retry = true + noRetry = false ) func (werl *workspaceResourceEventLoop) processRepositoryCredential(ctx context.Context, req ctrl.Request, apiNamespaceClient client.Client) { @@ -81,14 +84,26 @@ func newWorkspaceResourceLoop(sharedResourceLoop *shared_resource_loop.SharedRes inputChannel: make(chan workspaceResourceLoopMessage), } - go internalWorkspaceResourceEventLoop(workspaceResourceEventLoop.inputChannel, sharedResourceLoop, workspaceEventLoopInputChannel) + go internalWorkspaceResourceEventLoop(workspaceResourceEventLoop.inputChannel, sharedResourceLoop, workspaceEventLoopInputChannel, shared_resource_loop.DefaultK8sClientFactory{}) + + return workspaceResourceEventLoop +} + +func newWorkspaceResourceLoopWithFactory(sharedResourceLoop *shared_resource_loop.SharedResourceEventLoop, + workspaceEventLoopInputChannel chan workspaceEventLoopMessage, k8sClientFactory shared_resource_loop.SRLK8sClientFactory) *workspaceResourceEventLoop { + + workspaceResourceEventLoop := &workspaceResourceEventLoop{ + inputChannel: make(chan workspaceResourceLoopMessage), + } + + go internalWorkspaceResourceEventLoop(workspaceResourceEventLoop.inputChannel, sharedResourceLoop, workspaceEventLoopInputChannel, k8sClientFactory) return workspaceResourceEventLoop } func internalWorkspaceResourceEventLoop(inputChan chan workspaceResourceLoopMessage, sharedResourceLoop *shared_resource_loop.SharedResourceEventLoop, - workspaceEventLoopInputChannel chan workspaceEventLoopMessage) { + workspaceEventLoopInputChannel chan workspaceEventLoopMessage, k8sClientFactory shared_resource_loop.SRLK8sClientFactory) { ctx := context.Background() l := log.FromContext(ctx). @@ -142,6 +157,7 @@ func internalWorkspaceResourceEventLoop(inputChan chan workspaceResourceLoopMess log: l, sharedResourceLoop: sharedResourceLoop, workspaceEventLoopInputChannel: workspaceEventLoopInputChannel, + k8sClientFactory: k8sClientFactory, } taskRetryLoop.AddTaskIfNotPresent(mapKey, task, sharedutil.ExponentialBackoff{Factor: 2, Min: time.Millisecond * 200, Max: time.Second * 10, Jitter: true}) @@ -154,12 +170,13 @@ type workspaceResourceEventTask struct { log logr.Logger sharedResourceLoop *shared_resource_loop.SharedResourceEventLoop workspaceEventLoopInputChannel chan workspaceEventLoopMessage + k8sClientFactory shared_resource_loop.SRLK8sClientFactory } // Returns true if the task should be retried, false otherwise, plus an error func (wert *workspaceResourceEventTask) PerformTask(taskContext context.Context) (bool, error) { - retry, err := internalProcessWorkspaceResourceMessage(taskContext, wert.msg, wert.sharedResourceLoop, wert.workspaceEventLoopInputChannel, wert.dbQueries, wert.log) + retry, err := internalProcessWorkspaceResourceMessage(taskContext, wert.msg, wert.sharedResourceLoop, wert.workspaceEventLoopInputChannel, wert.dbQueries, wert.k8sClientFactory, wert.log) // If we recognize this error is a connection error due to the user providing us invalid credentials, don't bother to log it. if application_event_loop.IsManagedEnvironmentConnectionUserError(err, wert.log) { @@ -173,8 +190,7 @@ func (wert *workspaceResourceEventTask) PerformTask(taskContext context.Context) // Returns true if the task should be retried, false otherwise, plus an error func internalProcessWorkspaceResourceMessage(ctx context.Context, msg workspaceResourceLoopMessage, sharedResourceLoop *shared_resource_loop.SharedResourceEventLoop, workspaceEventLoopInputChannel chan workspaceEventLoopMessage, - dbQueries db.DatabaseQueries, log logr.Logger) (bool, error) { - const retry, noRetry = true, false + dbQueries db.DatabaseQueries, k8sClientFactory shared_resource_loop.SRLK8sClientFactory, log logr.Logger) (bool, error) { log.V(logutil.LogLevel_Debug).Info("processWorkspaceResource received message: " + string(msg.messageType)) @@ -184,87 +200,107 @@ func internalProcessWorkspaceResourceMessage(ctx context.Context, msg workspaceR // When the event is related to 'GitOpsDeploymentRepositoryCredential' resource, we need to process the event if msg.messageType == workspaceResourceLoopMessageType_processRepositoryCredential { - req, ok := (msg.payload).(ctrl.Request) - if !ok { - return noRetry, fmt.Errorf("invalid payload in processWorkspaceResourceMessage") + shouldRetry, err := handleResourceLoopRepositoryCredential(ctx, msg, sharedResourceLoop, k8sClientFactory, log) + if err != nil { + return shouldRetry, fmt.Errorf("failed to process workspace resource message: %v", err) } - // Retrieve the namespace that the repository credential is contained within - namespace := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: req.Namespace, - }, + return noRetry, nil + } else if msg.messageType == workspaceResourceLoopMessageType_processManagedEnvironment { + + shouldRetry, err := handleResourceLoopManagedEnvironment(ctx, msg, sharedResourceLoop, k8sClientFactory, workspaceEventLoopInputChannel, log) + if err != nil { + return shouldRetry, fmt.Errorf("failed to process workspace resource message: %v", err) } - if err := msg.apiNamespaceClient.Get(ctx, client.ObjectKeyFromObject(namespace), namespace); err != nil { - if !apierr.IsNotFound(err) { - return retry, fmt.Errorf("unexpected error in retrieving repo credentials: %v", err) - } + return noRetry, nil - log.V(logutil.LogLevel_Warn).Info("Received a message for a repository credential in a namepace that doesn't exist", "namespace", namespace) - return noRetry, nil - } + } + return noRetry, fmt.Errorf("SEVERE: unrecognized sharedResourceLoopMessageType: %s", msg.messageType) - // Request that the shared resource loop handle the GitOpsDeploymentRepositoryCredential resource: - // - If the GitOpsDeploymentRepositoryCredential doesn't exist, delete the corresponding database table - // - If the GitOpsDeploymentRepositoryCredential does exist, but not in the DB, then create a RepositoryCredential DB entry - // - If the GitOpsDeploymentRepositoryCredential does exist, and also in the DB, then compare and change a RepositoryCredential DB entry - // Then, in all 3 cases, create an Operation to update the cluster-agent - _, err := sharedResourceLoop.ReconcileRepositoryCredential(ctx, msg.apiNamespaceClient, *namespace, req.Name, shared_resource_loop.DefaultK8sClientFactory{}) +} - if err != nil { - return retry, fmt.Errorf("unable to reconcile repository credential. Error: %v", err) +func handleResourceLoopRepositoryCredential(ctx context.Context, msg workspaceResourceLoopMessage, sharedResourceLoop *shared_resource_loop.SharedResourceEventLoop, k8sClientFactory shared_resource_loop.SRLK8sClientFactory, log logr.Logger) (bool, error) { + + req, ok := (msg.payload).(ctrl.Request) + if !ok { + return noRetry, fmt.Errorf("invalid RepositoryCredential payload in processWorkspaceResourceMessage") + } + + // Retrieve the namespace that the repository credential is contained within + namespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: req.Namespace, + }, + } + if err := msg.apiNamespaceClient.Get(ctx, client.ObjectKeyFromObject(namespace), namespace); err != nil { + + if !apierr.IsNotFound(err) { + return retry, fmt.Errorf("unexpected error in retrieving repo credentials: %v", err) } + log.V(logutil.LogLevel_Warn).Info("Received a message for a repository credential in a namepace that doesn't exist", "namespace", namespace) return noRetry, nil - } else if msg.messageType == workspaceResourceLoopMessageType_processManagedEnvironment { + } - evlMessage, ok := (msg.payload).(eventlooptypes.EventLoopMessage) - if !ok { - return noRetry, fmt.Errorf("invalid payload in processWorkspaceResourceMessage") - } + // Request that the shared resource loop handle the GitOpsDeploymentRepositoryCredential resource: + // - If the GitOpsDeploymentRepositoryCredential doesn't exist, delete the corresponding database table + // - If the GitOpsDeploymentRepositoryCredential does exist, but not in the DB, then create a RepositoryCredential DB entry + // - If the GitOpsDeploymentRepositoryCredential does exist, and also in the DB, then compare and change a RepositoryCredential DB entry + // Then, in all 3 cases, create an Operation to update the cluster-agent + _, err := sharedResourceLoop.ReconcileRepositoryCredential(ctx, msg.apiNamespaceClient, *namespace, req.Name, k8sClientFactory) - if evlMessage.Event == nil { // Sanity test the message - log.Error(nil, "SEVERE: process managed env event is nil") - } + if err != nil { + return retry, fmt.Errorf("unable to reconcile repository credential. Error: %v", err) + } - req := evlMessage.Event.Request + return noRetry, nil +} - // Retrieve the namespace that the managed environment is contained within - namespace := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: req.Namespace, - }, - } - if err := msg.apiNamespaceClient.Get(ctx, client.ObjectKeyFromObject(namespace), namespace); err != nil { +func handleResourceLoopManagedEnvironment(ctx context.Context, msg workspaceResourceLoopMessage, sharedResourceLoop *shared_resource_loop.SharedResourceEventLoop, k8sClientFactory shared_resource_loop.SRLK8sClientFactory, workspaceEventLoopInputChannel chan workspaceEventLoopMessage, log logr.Logger) (bool, error) { - if !apierr.IsNotFound(err) { - return retry, fmt.Errorf("unexpected error in retrieving namespace of managed env CR: %v", err) - } + evlMessage, ok := (msg.payload).(eventlooptypes.EventLoopMessage) + if !ok { + return noRetry, fmt.Errorf("invalid ManagedEnvironment payload in processWorkspaceResourceMessage") + } - log.V(logutil.LogLevel_Warn).Info("Received a message for a managed end CR in a namespace that doesn't exist", "namespace", namespace) - return noRetry, nil - } + if evlMessage.Event == nil { // Sanity test the message + log.Error(nil, "SEVERE: process managed env event is nil") + } - // Ask the shared resource loop to ensure the managed environment is reconciled - _, err := sharedResourceLoop.ReconcileSharedManagedEnv(ctx, msg.apiNamespaceClient, *namespace, req.Name, req.Namespace, - false, shared_resource_loop.DefaultK8sClientFactory{}, log) - if err != nil { - return retry, fmt.Errorf("unable to reconcile shared managed env: %v", err) - } + req := evlMessage.Event.Request - // Once we finish processing the managed environment, send it back to the workspace event loop, so it can be passed to GitOpsDeployments. - // - Send it on another go routine to keep from blocking this one - go func() { - workspaceEventLoopInputChannel <- workspaceEventLoopMessage{ - messageType: workspaceEventLoopMessageType_managedEnvProcessed_Event, - payload: evlMessage, - } - }() + // Retrieve the namespace that the managed environment is contained within + namespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: req.Namespace, + }, + } + if err := msg.apiNamespaceClient.Get(ctx, client.ObjectKeyFromObject(namespace), namespace); err != nil { + if !apierr.IsNotFound(err) { + return retry, fmt.Errorf("unexpected error in retrieving namespace of managed env CR: %v", err) + } + + log.V(logutil.LogLevel_Warn).Info("Received a message for a managed end CR in a namespace that doesn't exist", "namespace", namespace) return noRetry, nil + } + // Ask the shared resource loop to ensure the managed environment is reconciled + _, err := sharedResourceLoop.ReconcileSharedManagedEnv(ctx, msg.apiNamespaceClient, *namespace, req.Name, req.Namespace, + false, k8sClientFactory, log) + if err != nil { + return retry, fmt.Errorf("unable to reconcile shared managed env: %v", err) } - return noRetry, fmt.Errorf("SEVERE: unrecognized sharedResourceLoopMessageType: %s " + string(msg.messageType)) + // Once we finish processing the managed environment, send it back to the workspace event loop, so it can be passed to GitOpsDeployments. + // - Send it on another go routine to keep from blocking this one + go func() { + workspaceEventLoopInputChannel <- workspaceEventLoopMessage{ + messageType: workspaceEventLoopMessageType_managedEnvProcessed_Event, + payload: evlMessage, + } + }() + + return noRetry, nil } diff --git a/backend/eventloop/workspace_resource_event_loop_test.go b/backend/eventloop/workspace_resource_event_loop_test.go new file mode 100644 index 0000000000..7e9d04c197 --- /dev/null +++ b/backend/eventloop/workspace_resource_event_loop_test.go @@ -0,0 +1,379 @@ +package eventloop + +import ( + "context" + "fmt" + "time" + + "github.com/go-logr/logr" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/uuid" + + managedgitopsv1alpha1 "github.com/redhat-appstudio/managed-gitops/backend-shared/apis/managed-gitops/v1alpha1" + db "github.com/redhat-appstudio/managed-gitops/backend-shared/db" + "github.com/redhat-appstudio/managed-gitops/backend-shared/util/tests" + "github.com/redhat-appstudio/managed-gitops/backend/eventloop/eventlooptypes" + "github.com/redhat-appstudio/managed-gitops/backend/eventloop/shared_resource_loop" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +var _ = Describe("Test Workspace Resource Loop", func() { + Context("Testing WorkspaceResourceLoop", func() { + var ( + workspaceChan chan workspaceEventLoopMessage + inputChan chan workspaceResourceLoopMessage + k8sClient client.Client + ns *corev1.Namespace + ) + BeforeEach(func() { + err := db.SetupForTestingDBGinkgo() + Expect(err).ToNot(HaveOccurred()) + + scheme, + argocdNamespace, + kubesystemNamespace, + apiNamespace, + err := tests.GenericTestSetup() + Expect(err).ToNot(HaveOccurred()) + + ns = apiNamespace + + k8sClient = fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(apiNamespace, argocdNamespace, kubesystemNamespace). + Build() + + sharedResourceLoop := shared_resource_loop.NewSharedResourceLoop() + workspaceChan = make(chan workspaceEventLoopMessage) + wel := newWorkspaceResourceLoopWithFactory(sharedResourceLoop, workspaceChan, MockSRLK8sClientFactory{fakeClient: k8sClient}) + Expect(wel).ToNot(BeNil()) + + inputChan = wel.inputChannel + }) + + It("should handle a valid message of type ManagedEnvironment", func() { + By("create a resource loop message for GitOpsDeploymentManagedEnvironment") + wrlm := workspaceResourceLoopMessage{ + apiNamespaceClient: k8sClient, + messageType: workspaceResourceLoopMessageType_processManagedEnvironment, + payload: eventlooptypes.EventLoopMessage{ + MessageType: eventlooptypes.ApplicationEventLoopMessageType_Event, + Event: &eventlooptypes.EventLoopEvent{ + ReqResource: eventlooptypes.GitOpsDeploymentManagedEnvironmentTypeName, + Client: k8sClient, + EventType: eventlooptypes.ManagedEnvironmentModified, + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: ns.Name, + Name: "sample", + }, + }, + }, + }, + } + + inputChan <- wrlm + + By("verify if the GitOpsDeploymentManagedEnvironment was managed by the resource loop") + response := <-workspaceChan + Expect(response).ToNot(BeNil()) + Expect(response.messageType).To(Equal(workspaceEventLoopMessageType_managedEnvProcessed_Event)) + Expect(response.payload).To(Equal(wrlm.payload)) + + }) + + It("should handle a valid message of type RepositoryCredentials", func() { + dbQueries, err := db.NewUnsafePostgresDBQueries(false, true) + Expect(err).ToNot(HaveOccurred()) + defer dbQueries.CloseDatabase() + + ctx := context.Background() + + By("create a sample GitOpsDeploymentRepositoryCredential CR") + cr := &managedgitopsv1alpha1.GitOpsDeploymentRepositoryCredential{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-gitopsdeploymenrepositorycredential", + Namespace: ns.Name, + UID: uuid.NewUUID(), + }, + Spec: managedgitopsv1alpha1.GitOpsDeploymentRepositoryCredentialSpec{ + Repository: "https://fakegithub.com/test/test-repository", + Secret: "test-secret", + }} + + err = k8sClient.Create(ctx, cr) + Expect(err).ToNot(HaveOccurred()) + + // Create new Secret + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-secret", + Namespace: ns.Name, + }, + Data: map[string][]byte{ + "username": []byte("test-username"), + "password": []byte("test-password"), + }, + } + err = k8sClient.Create(ctx, secret) + Expect(err).ToNot(HaveOccurred()) + + By("send a message to the resource loop") + wrlm := workspaceResourceLoopMessage{ + apiNamespaceClient: k8sClient, + messageType: workspaceResourceLoopMessageType_processRepositoryCredential, + payload: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: cr.Namespace, + Name: cr.Name, + }, + }, + } + + inputChan <- wrlm + + By("verify if the resource loop has handled the RepositoryCredential") + apiCRToDBList := &[]db.APICRToDatabaseMapping{} + Eventually(func() bool { + err = dbQueries.ListAPICRToDatabaseMappingByAPINamespaceAndName(ctx, db.APICRToDatabaseMapping_ResourceType_GitOpsDeploymentRepositoryCredential, cr.Name, cr.Namespace, string(ns.UID), db.APICRToDatabaseMapping_DBRelationType_RepositoryCredential, apiCRToDBList) + Expect(err).ToNot(HaveOccurred()) + for _, apiCRToDB := range *apiCRToDBList { + if apiCRToDB.APIResourceName == cr.Name && apiCRToDB.APIResourceNamespace == cr.Namespace { + return true + } + } + return false + }, "30s", "50ms").Should(BeTrue()) + + for _, apiCRToDB := range *apiCRToDBList { + if apiCRToDB.APIResourceName == cr.Name && apiCRToDB.APIResourceNamespace == cr.Namespace { + Expect(apiCRToDB.DBRelationType).To(Equal(db.APICRToDatabaseMapping_DBRelationType_RepositoryCredential)) + Expect(apiCRToDB.APIResourceType).To(Equal(db.APICRToDatabaseMapping_ResourceType_GitOpsDeploymentRepositoryCredential)) + Expect(apiCRToDB.APIResourceUID).To(Equal(string(cr.UID))) + } + } + + err = k8sClient.Delete(ctx, cr) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Delete(ctx, secret) + Expect(err).ToNot(HaveOccurred()) + }) + }) + + Context("Test internalProcessWorkspaceResourceMessage function", func() { + var ( + ctx context.Context + workspaceChan chan workspaceEventLoopMessage + k8sClient client.Client + dbQueries db.AllDatabaseQueries + sharedResourceLoop *shared_resource_loop.SharedResourceEventLoop + mockClientFactory shared_resource_loop.SRLK8sClientFactory + testLog logr.Logger + ns *corev1.Namespace + ) + + BeforeEach(func() { + ctx = context.Background() + testLog = log.FromContext(ctx) + + err := db.SetupForTestingDBGinkgo() + Expect(err).ToNot(HaveOccurred()) + + dbQueries, err = db.NewUnsafePostgresDBQueries(false, true) + Expect(err).ToNot(HaveOccurred()) + + scheme, + argocdNamespace, + kubesystemNamespace, + apiNamespace, + err := tests.GenericTestSetup() + Expect(err).ToNot(HaveOccurred()) + ns = apiNamespace + + k8sClient = fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(apiNamespace, argocdNamespace, kubesystemNamespace). + Build() + + sharedResourceLoop = shared_resource_loop.NewSharedResourceLoop() + workspaceChan = make(chan workspaceEventLoopMessage) + mockClientFactory = MockSRLK8sClientFactory{fakeClient: k8sClient} + }) + + AfterEach(func() { + dbQueries.CloseDatabase() + }) + + It("should return an error with no retry if the k8sClient is nil", func() { + msg := workspaceResourceLoopMessage{ + messageType: workspaceResourceLoopMessageType_processRepositoryCredential, + payload: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "test-ns", + Name: "test", + }, + }, + } + shouldRetry, err := internalProcessWorkspaceResourceMessage(ctx, msg, sharedResourceLoop, workspaceChan, dbQueries, mockClientFactory, testLog) + + Expect(shouldRetry).To(BeFalse()) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("invalid namespace client")) + }) + + It("should return an error with no retry for an invalid message", func() { + msg := workspaceResourceLoopMessage{ + messageType: workspaceResourceLoopMessageType("unknown-message-type"), + apiNamespaceClient: k8sClient, + payload: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "test-ns", + Name: "test", + }, + }, + } + shouldRetry, err := internalProcessWorkspaceResourceMessage(ctx, msg, sharedResourceLoop, workspaceChan, dbQueries, mockClientFactory, testLog) + + Expect(shouldRetry).To(BeFalse()) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("SEVERE: unrecognized sharedResourceLoopMessageType: unknown-message-type")) + }) + + resourceLoopErr := func(msg string) error { + return fmt.Errorf("failed to process workspace resource message: %v", msg) + } + + Context("Test ManagedEnvironment messages", func() { + var ( + msg workspaceResourceLoopMessage + ) + + BeforeEach(func() { + msg = workspaceResourceLoopMessage{ + apiNamespaceClient: k8sClient, + messageType: workspaceResourceLoopMessageType_processManagedEnvironment, + payload: eventlooptypes.EventLoopMessage{ + MessageType: eventlooptypes.ApplicationEventLoopMessageType_Event, + Event: &eventlooptypes.EventLoopEvent{ + ReqResource: eventlooptypes.GitOpsDeploymentManagedEnvironmentTypeName, + Client: k8sClient, + EventType: eventlooptypes.ManagedEnvironmentModified, + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: ns.Name, + Name: "sample", + }, + }, + }, + }, + } + }) + + It("should return an error with no retry if the ManagedEnvironment msg has an invalid payload", func() { + msg.payload = "invalid-payload" + + shouldRetry, err := internalProcessWorkspaceResourceMessage(ctx, msg, sharedResourceLoop, workspaceChan, dbQueries, mockClientFactory, testLog) + + Expect(shouldRetry).To(BeFalse()) + Expect(err).To(HaveOccurred()) + expectedErr := "invalid ManagedEnvironment payload in processWorkspaceResourceMessage" + Expect(err).To(Equal(resourceLoopErr(expectedErr))) + }) + + It("should not return an error with no retry if the ManagedEnvironment namespace is not found", func() { + msg.payload = eventlooptypes.EventLoopMessage{ + MessageType: eventlooptypes.ApplicationEventLoopMessageType_Event, + Event: &eventlooptypes.EventLoopEvent{ + ReqResource: eventlooptypes.GitOpsDeploymentManagedEnvironmentTypeName, + Client: k8sClient, + Request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "invalid-ns", + Name: "sample", + }, + }, + }, + } + + shouldRetry, err := internalProcessWorkspaceResourceMessage(ctx, msg, sharedResourceLoop, workspaceChan, dbQueries, mockClientFactory, testLog) + + Expect(shouldRetry).To(BeFalse()) + Expect(err).ToNot(HaveOccurred()) + }) + + It("should return an error with retry if the shared resource loop fails to process ManagedEnvironment", func() { + expiredCtx, cancel := context.WithDeadline(ctx, time.Now().Add(-2*time.Hour)) + cancel() + + shouldRetry, err := internalProcessWorkspaceResourceMessage(expiredCtx, msg, sharedResourceLoop, workspaceChan, dbQueries, mockClientFactory, testLog) + + Expect(shouldRetry).To(BeTrue()) + Expect(err).To(HaveOccurred()) + expectedErr := "unable to reconcile shared managed env: context cancelled in GetOrCreateSharedManagedEnv" + Expect(err).To(Equal(resourceLoopErr(expectedErr))) + }) + }) + + Context("Test RepositoryCredential messages", func() { + var ( + msg workspaceResourceLoopMessage + ) + + BeforeEach(func() { + msg = workspaceResourceLoopMessage{ + messageType: workspaceResourceLoopMessageType_processRepositoryCredential, + apiNamespaceClient: k8sClient, + payload: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: ns.Name, + Name: "test", + }, + }, + } + }) + + It("should return an error with no retry if the RepositoryCredential msg has an invalid payload", func() { + msg.payload = "invalid-payload" + shouldRetry, err := internalProcessWorkspaceResourceMessage(ctx, msg, sharedResourceLoop, workspaceChan, dbQueries, mockClientFactory, testLog) + + Expect(shouldRetry).To(BeFalse()) + Expect(err).To(HaveOccurred()) + expectedErr := "invalid RepositoryCredential payload in processWorkspaceResourceMessage" + Expect(err).To(Equal(resourceLoopErr(expectedErr))) + }) + + It("should not return an error with no retry if the RepositoryCredential namespace is not found", func() { + msg.payload = reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "test-ns", + Name: "test", + }, + } + shouldRetry, err := internalProcessWorkspaceResourceMessage(ctx, msg, sharedResourceLoop, workspaceChan, dbQueries, mockClientFactory, testLog) + + Expect(shouldRetry).To(BeFalse()) + Expect(err).ToNot(HaveOccurred()) + }) + + It("should return an error with retry if the shared resource loop fails to process RepositoryCredential", func() { + expiredCtx, cancel := context.WithDeadline(ctx, time.Now().Add(-2*time.Hour)) + cancel() + + shouldRetry, err := internalProcessWorkspaceResourceMessage(expiredCtx, msg, sharedResourceLoop, workspaceChan, dbQueries, mockClientFactory, testLog) + + Expect(shouldRetry).To(BeTrue()) + Expect(err).To(HaveOccurred()) + expectedErr := "unable to reconcile repository credential. Error: context cancelled in ReconcileRepositoryCredential" + Expect(err).To(Equal(resourceLoopErr(expectedErr))) + }) + }) + }) +})