From c896cf4416444b6144f03751597c95676585c564 Mon Sep 17 00:00:00 2001 From: Vibhav Bobade Date: Tue, 30 Jun 2026 17:35:08 +0530 Subject: [PATCH] fix(controlplane): fallback CAS downloads from invalid backends Signed-off-by: Vibhav Bobade --- app/controlplane/cmd/wire_gen.go | 2 +- .../internal/service/cascredential.go | 5 +- .../internal/service/casredirect.go | 14 ++--- app/controlplane/pkg/biz/casbackend.go | 14 +++++ app/controlplane/pkg/biz/casbackend_test.go | 56 +++++++++++++++++++ 5 files changed, 82 insertions(+), 9 deletions(-) diff --git a/app/controlplane/cmd/wire_gen.go b/app/controlplane/cmd/wire_gen.go index 8c3626bd6..81bed65d8 100644 --- a/app/controlplane/cmd/wire_gen.go +++ b/app/controlplane/cmd/wire_gen.go @@ -295,7 +295,7 @@ func wireApp(contextContext context.Context, bootstrap *conf.Bootstrap, readerWr integrationsService := service.NewIntegrationsService(integrationUseCase, workflowUseCase, availablePlugins, v5...) organizationService := service.NewOrganizationService(membershipUseCase, organizationUseCase, v5...) casBackendService := service.NewCASBackendService(casBackendUseCase, providers, v5...) - casRedirectService, err := service.NewCASRedirectService(casMappingUseCase, casCredentialsUseCase, bootstrap_CASServer, v5...) + casRedirectService, err := service.NewCASRedirectService(casMappingUseCase, casCredentialsUseCase, casBackendUseCase, bootstrap_CASServer, v5...) if err != nil { cleanup3() cleanup2() diff --git a/app/controlplane/internal/service/cascredential.go b/app/controlplane/internal/service/cascredential.go index 4ec74786d..3d0682cf2 100644 --- a/app/controlplane/internal/service/cascredential.go +++ b/app/controlplane/internal/service/cascredential.go @@ -130,7 +130,10 @@ func (s *CASCredentialsService) Get(ctx context.Context, req *pb.CASCredentialsS } if mapping != nil { - backend = mapping.CASBackend + backend, err = s.casBackendUC.FindDownloadBackend(ctx, mapping.CASBackend) + if err != nil { + return nil, handleUseCaseErr(err, s.log) + } } else { // fallback to default backend if the user or the token is allowed to if ok, err := s.authzUC.Enforce(ctx, currentAuthzSubject, authz.PolicyDefaultBackendArtifactRead); err != nil { diff --git a/app/controlplane/internal/service/casredirect.go b/app/controlplane/internal/service/casredirect.go index 9f0dc136c..35028a2fe 100644 --- a/app/controlplane/internal/service/casredirect.go +++ b/app/controlplane/internal/service/casredirect.go @@ -49,10 +49,11 @@ type CASRedirectService struct { casMappingUC *biz.CASMappingUseCase casCredsUseCase *biz.CASCredentialsUseCase + casBackendUC *biz.CASBackendUseCase casServerConf *conf.Bootstrap_CASServer } -func NewCASRedirectService(casmUC *biz.CASMappingUseCase, casCredsUC *biz.CASCredentialsUseCase, conf *conf.Bootstrap_CASServer, opts ...NewOpt) (*CASRedirectService, error) { +func NewCASRedirectService(casmUC *biz.CASMappingUseCase, casCredsUC *biz.CASCredentialsUseCase, casBackendUC *biz.CASBackendUseCase, conf *conf.Bootstrap_CASServer, opts ...NewOpt) (*CASRedirectService, error) { if conf == nil || conf.GetDownloadUrl() == "" { return nil, errors.New("CASServer.downloadURL configuration is missing") } @@ -61,6 +62,7 @@ func NewCASRedirectService(casmUC *biz.CASMappingUseCase, casCredsUC *biz.CASCre service: newService(opts...), casMappingUC: casmUC, casCredsUseCase: casCredsUC, + casBackendUC: casBackendUC, casServerConf: conf, }, nil } @@ -103,18 +105,16 @@ func (s *CASRedirectService) GetDownloadURL(ctx context.Context, req *pb.GetDown return nil, handleUseCaseErr(err, s.log) } - backend := mapping.CASBackend + backend, err := s.casBackendUC.FindDownloadBackend(ctx, mapping.CASBackend) + if err != nil { + return nil, handleUseCaseErr(err, s.log) + } // inline backends don't have a download URL if backend.Inline { return nil, kerrors.NotFound("not found", "CAS backend is inline") } - // check if the backend is on a valid state, if not return an error - if backend.ValidationStatus != biz.CASBackendValidationOK { - return nil, pb.ErrorCasBackendErrorReasonInvalid("CAS Storage is in an invalid state and can't download artifacts, please fix it before attempting it again") - } - // Create an URL to download the artifact from the CAS backend downloadBase, err := url.Parse(s.casServerConf.GetDownloadUrl()) if err != nil { diff --git a/app/controlplane/pkg/biz/casbackend.go b/app/controlplane/pkg/biz/casbackend.go index bdb94632a..b5669007f 100644 --- a/app/controlplane/pkg/biz/casbackend.go +++ b/app/controlplane/pkg/biz/casbackend.go @@ -350,6 +350,20 @@ func (uc *CASBackendUseCase) FindDefaultOrFallbackBackend(ctx context.Context, o return fallbackBackend, nil } +// FindDownloadBackend returns the mapped backend, or the org default/fallback when the mapped backend is invalid. +func (uc *CASBackendUseCase) FindDownloadBackend(ctx context.Context, mapped *CASBackend) (*CASBackend, error) { + if mapped == nil { + return nil, NewErrNotFound("CAS Backend") + } + + if mapped.ValidationStatus == CASBackendValidationOK { + return mapped, nil + } + + uc.logger.Infow("msg", "mapped CAS backend validation failed, attempting default/fallback", "backend", mapped.Name, "status", mapped.ValidationStatus, "orgID", mapped.OrganizationID) + return uc.FindDefaultOrFallbackBackend(ctx, mapped.OrganizationID.String()) +} + func (uc *CASBackendUseCase) CreateInlineBackend(ctx context.Context, orgID string) (*CASBackend, error) { ctx, span := otelx.Start(ctx, casBackendTracer, "CASBackendUseCase.CreateInlineBackend") defer span.End() diff --git a/app/controlplane/pkg/biz/casbackend_test.go b/app/controlplane/pkg/biz/casbackend_test.go index f8da92601..2bd936c2e 100644 --- a/app/controlplane/pkg/biz/casbackend_test.go +++ b/app/controlplane/pkg/biz/casbackend_test.go @@ -72,6 +72,62 @@ func (s *casBackendTestSuite) TestFindDefaultBackendFound() { assert.Equal(backend, wantBackend) } +func (s *casBackendTestSuite) TestFindDownloadBackend() { + ctx := context.Background() + mapped := &biz.CASBackend{ID: uuid.New(), OrganizationID: s.validUUID, ValidationStatus: biz.CASBackendValidationOK} + + got, err := s.useCase.FindDownloadBackend(ctx, mapped) + s.Require().NoError(err) + s.Same(mapped, got) + + s.resetMock() + mapped.ValidationStatus = biz.CASBackendValidationFailed + defaultBackend := &biz.CASBackend{ID: uuid.New(), ValidationStatus: biz.CASBackendValidationOK} + s.repo.On("FindDefaultBackend", mock.Anything, s.validUUID).Return(defaultBackend, nil).Once() + + got, err = s.useCase.FindDownloadBackend(ctx, mapped) + s.Require().NoError(err) + s.Same(defaultBackend, got) + + s.resetMock() + defaultBackend.ValidationStatus = biz.CASBackendValidationFailed + fallbackBackend := &biz.CASBackend{ID: uuid.New(), ValidationStatus: biz.CASBackendValidationOK} + s.repo.On("FindDefaultBackend", mock.Anything, s.validUUID).Return(defaultBackend, nil).Once() + s.repo.On("FindFallbackBackend", mock.Anything, s.validUUID).Return(fallbackBackend, nil).Once() + + got, err = s.useCase.FindDownloadBackend(ctx, mapped) + s.Require().NoError(err) + s.Same(fallbackBackend, got) + + got, err = s.useCase.FindDownloadBackend(ctx, nil) + s.Nil(got) + s.True(biz.IsNotFound(err)) + + s.resetMock() + s.repo.On("FindDefaultBackend", mock.Anything, s.validUUID).Return(nil, nil).Once() + + got, err = s.useCase.FindDownloadBackend(ctx, mapped) + s.Nil(got) + s.True(biz.IsNotFound(err)) + + s.resetMock() + s.repo.On("FindDefaultBackend", mock.Anything, s.validUUID).Return(defaultBackend, nil).Once() + s.repo.On("FindFallbackBackend", mock.Anything, s.validUUID).Return(nil, nil).Once() + + got, err = s.useCase.FindDownloadBackend(ctx, mapped) + s.Nil(got) + s.True(biz.IsNotFound(err)) + + s.resetMock() + fallbackBackend.ValidationStatus = biz.CASBackendValidationFailed + s.repo.On("FindDefaultBackend", mock.Anything, s.validUUID).Return(defaultBackend, nil).Once() + s.repo.On("FindFallbackBackend", mock.Anything, s.validUUID).Return(fallbackBackend, nil).Once() + + got, err = s.useCase.FindDownloadBackend(ctx, mapped) + s.Nil(got) + s.True(biz.IsErrValidation(err)) +} + func (s *casBackendTestSuite) TestSaveInvalidUUID() { repo, err := s.useCase.CreateOrUpdate(context.Background(), s.invalidUUID, "", "", "", backendType, true) assert.True(s.T(), biz.IsErrInvalidUUID(err))