diff --git a/app/artifact-cas/internal/server/grpc.go b/app/artifact-cas/internal/server/grpc.go index 1a92dfa7b..b5471eb3b 100644 --- a/app/artifact-cas/internal/server/grpc.go +++ b/app/artifact-cas/internal/server/grpc.go @@ -19,6 +19,7 @@ import ( "context" "fmt" "os" + "regexp" v1 "github.com/chainloop-dev/chainloop/app/artifact-cas/api/cas/v1" "github.com/chainloop-dev/chainloop/app/artifact-cas/internal/conf" @@ -27,6 +28,7 @@ import ( "github.com/getsentry/sentry-go" "github.com/go-kratos/kratos/v2/errors" jwtMiddleware "github.com/go-kratos/kratos/v2/middleware/auth/jwt" + "github.com/go-kratos/kratos/v2/middleware/selector" jwt "github.com/golang-jwt/jwt/v4" "google.golang.org/genproto/googleapis/bytestream" @@ -62,11 +64,14 @@ func NewGRPCServer(c *conf.Server, authConf *conf.Auth, byteService *service.Byt ), logging.Server(logger), // NOTE: JWT middleware only works for unary requests - // below you can see a reimplementation of the middleware as a stream interceptor - jwtMiddleware.Server( - loadPublicKey(rawKey), - jwtMiddleware.WithSigningMethod(casJWT.SigningMethod), - jwtMiddleware.WithClaims(func() jwt.Claims { return &casJWT.Claims{} })), + // below you can see a re-implementation of the middleware as a stream interceptor + // If we require a logged in user we + selector.Server( + jwtMiddleware.Server( + loadPublicKey(rawKey), + jwtMiddleware.WithSigningMethod(casJWT.SigningMethod), + jwtMiddleware.WithClaims(func() jwt.Claims { return &casJWT.Claims{} })), + ).Match(requireAuthentication()).Build(), validate.Validator(), ), @@ -92,6 +97,7 @@ func NewGRPCServer(c *conf.Server, authConf *conf.Auth, byteService *service.Byt bytestream.RegisterByteStreamServer(srv.Server, byteService) v1.RegisterResourceServiceServer(srv.Server, rSvc) + v1.RegisterStatusServiceServer(srv.Server, service.NewStatusService(Version)) // Register and set metrics to 0 grpc_prometheus.Register(srv.Server) @@ -99,6 +105,16 @@ func NewGRPCServer(c *conf.Server, authConf *conf.Auth, byteService *service.Byt return srv, nil } +func requireAuthentication() selector.MatchFunc { + // Skip authentication on the status grpc service + const skipRegexp = "(cas.v1.StatusService/.*)" + + return func(ctx context.Context, operation string) bool { + r := regexp.MustCompile(skipRegexp) + return !r.MatchString(operation) + } +} + // load key for verification func loadPublicKey(rawKey []byte) jwt.Keyfunc { return func(token *jwt.Token) (interface{}, error) { diff --git a/app/artifact-cas/internal/server/grpc_test.go b/app/artifact-cas/internal/server/grpc_test.go index 7b9bf4c92..ad2b94881 100644 --- a/app/artifact-cas/internal/server/grpc_test.go +++ b/app/artifact-cas/internal/server/grpc_test.go @@ -138,6 +138,23 @@ func TestJWTAuthFunc(t *testing.T) { } } +func TestRequireAuthentication(t *testing.T) { + testCases := []struct { + operation string + matches bool + }{ + {"/cas.v1.Resource/List", true}, + {"/cas.v1.Bytestream/List", true}, + {"/cas.v1.StatusService/Infoz", false}, + {"/cas.v1.StatusService/Statusz", false}, + } + + matchFunc := requireAuthentication() + for _, op := range testCases { + assert.Equal(t, matchFunc(context.Background(), op.operation), op.matches) + } +} + func loadTestPublicKey(path string) jwt.Keyfunc { rawKey, _ := os.ReadFile(path) return func(token *jwt.Token) (interface{}, error) { diff --git a/app/controlplane/cmd/wire.go b/app/controlplane/cmd/wire.go index b767f8b1e..83fc06877 100644 --- a/app/controlplane/cmd/wire.go +++ b/app/controlplane/cmd/wire.go @@ -47,6 +47,7 @@ func wireApp(*conf.Bootstrap, credentials.ReaderWriter, log.Logger) (*app, func( wire.Bind(new(biz.CASClient), new(*biz.CASClientUseCase)), oci.NewBackendProvider, serviceOpts, + wire.Value([]biz.CASClientOpts{}), wire.FieldsOf(new(*conf.Bootstrap), "Server", "Auth", "Data", "CasServer"), newApp, ), diff --git a/app/controlplane/cmd/wire_gen.go b/app/controlplane/cmd/wire_gen.go index 86bc6962f..f5817ed3b 100644 --- a/app/controlplane/cmd/wire_gen.go +++ b/app/controlplane/cmd/wire_gen.go @@ -55,31 +55,32 @@ func wireApp(bootstrap *conf.Bootstrap, readerWriter credentials.ReaderWriter, l robotAccountRepo := data.NewRobotAccountRepo(dataData, logger) auth := bootstrap.Auth robotAccountUseCase := biz.NewRootAccountUseCase(robotAccountRepo, workflowRepo, auth, logger) + casCredentialsUseCase, err := biz.NewCASCredentialsUseCase(auth) + if err != nil { + cleanup() + return nil, nil, err + } + bootstrap_CASServer := bootstrap.CasServer + v := _wireValue + casClientUseCase := biz.NewCASClientUseCase(casCredentialsUseCase, bootstrap_CASServer, logger, v...) workflowContractRepo := data.NewWorkflowContractRepo(dataData, logger) workflowContractUseCase := biz.NewWorkflowContractUseCase(workflowContractRepo, logger) workflowUseCase := biz.NewWorkflowUsecase(workflowRepo, workflowContractUseCase, logger) - v := serviceOpts(logger) - workflowService := service.NewWorkflowService(workflowUseCase, v...) + v2 := serviceOpts(logger) + workflowService := service.NewWorkflowService(workflowUseCase, v2...) confServer := bootstrap.Server - authService, err := service.NewAuthService(userUseCase, organizationUseCase, membershipUseCase, auth, confServer, v...) + authService, err := service.NewAuthService(userUseCase, organizationUseCase, membershipUseCase, auth, confServer, v2...) if err != nil { cleanup() return nil, nil, err } - robotAccountService := service.NewRobotAccountService(robotAccountUseCase, v...) + robotAccountService := service.NewRobotAccountService(robotAccountUseCase, v2...) workflowRunRepo := data.NewWorkflowRunRepo(dataData, logger) workflowRunUseCase, err := biz.NewWorkflowRunUseCase(workflowRunRepo, workflowRepo, logger) if err != nil { cleanup() return nil, nil, err } - casCredentialsUseCase, err := biz.NewCASCredentialsUseCase(auth) - if err != nil { - cleanup() - return nil, nil, err - } - bootstrap_CASServer := bootstrap.CasServer - casClientUseCase := biz.NewCASClientUseCase(casCredentialsUseCase, bootstrap_CASServer, logger) attestationUseCase := biz.NewAttestationUseCase(casClientUseCase, logger) newWorkflowRunServiceOpts := &service.NewWorkflowRunServiceOpts{ WorkflowRunUC: workflowRunUseCase, @@ -87,7 +88,7 @@ func wireApp(bootstrap *conf.Bootstrap, readerWriter credentials.ReaderWriter, l AttestationUC: attestationUseCase, WorkflowContractUC: workflowContractUseCase, CredsReader: readerWriter, - Opts: v, + Opts: v2, } workflowRunService := service.NewWorkflowRunService(newWorkflowRunServiceOpts) integration := dependencytrack.New(integrationUseCase, ociRepositoryUseCase, readerWriter, casClientUseCase, logger) @@ -101,26 +102,27 @@ func wireApp(bootstrap *conf.Bootstrap, readerWriter credentials.ReaderWriter, l IntegrationUseCase: integrationUseCase, CasCredsUseCase: casCredentialsUseCase, DepTrackUseCase: integration, - Opts: v, + Opts: v2, } attestationService := service.NewAttestationService(newAttestationServiceOpts) - workflowContractService := service.NewWorkflowSchemaService(workflowContractUseCase, v...) - contextService := service.NewContextService(ociRepositoryUseCase, v...) - casCredentialsService := service.NewCASCredentialsService(casCredentialsUseCase, ociRepositoryUseCase, v...) + workflowContractService := service.NewWorkflowSchemaService(workflowContractUseCase, v2...) + contextService := service.NewContextService(ociRepositoryUseCase, v2...) + casCredentialsService := service.NewCASCredentialsService(casCredentialsUseCase, ociRepositoryUseCase, v2...) orgMetricsRepo := data.NewOrgMetricsRepo(dataData, logger) orgMetricsUseCase, err := biz.NewOrgMetricsUseCase(orgMetricsRepo, logger) if err != nil { cleanup() return nil, nil, err } - orgMetricsService := service.NewOrgMetricsService(orgMetricsUseCase, v...) - ociRepositoryService := service.NewOCIRepositoryService(ociRepositoryUseCase, v...) - integrationsService := service.NewIntegrationsService(integrationUseCase, integration, workflowUseCase, v...) - organizationService := service.NewOrganizationService(membershipUseCase, v...) + orgMetricsService := service.NewOrgMetricsService(orgMetricsUseCase, v2...) + ociRepositoryService := service.NewOCIRepositoryService(ociRepositoryUseCase, v2...) + integrationsService := service.NewIntegrationsService(integrationUseCase, integration, workflowUseCase, v2...) + organizationService := service.NewOrganizationService(membershipUseCase, v2...) opts := &server.Opts{ UserUseCase: userUseCase, RobotAccountUseCase: robotAccountUseCase, OCIRepositoryUseCase: ociRepositoryUseCase, + CASClientUseCase: casClientUseCase, WorkflowSvc: workflowService, AuthSvc: authService, RobotAccountSvc: robotAccountService, @@ -155,6 +157,10 @@ func wireApp(bootstrap *conf.Bootstrap, readerWriter credentials.ReaderWriter, l }, nil } +var ( + _wireValue = []biz.CASClientOpts{} +) + // wire.go: func serviceOpts(l log.Logger) []service.NewOpt { diff --git a/app/controlplane/internal/biz/casclient.go b/app/controlplane/internal/biz/casclient.go index 490fc0ebe..c0a0072f7 100644 --- a/app/controlplane/internal/biz/casclient.go +++ b/app/controlplane/internal/biz/casclient.go @@ -17,6 +17,7 @@ package biz import ( "context" + "errors" "fmt" "io" @@ -29,9 +30,13 @@ import ( ) type CASClientUseCase struct { + // to generate temporary credentials credsProvider *CASCredentialsUseCase + // configuration to generate the client casServerConf *conf.Bootstrap_CASServer - logger *log.Helper + // factory to generate the client + casClientFactory CASClientFactory + logger *log.Helper } type CASUploader interface { @@ -45,11 +50,40 @@ type CASDownloader interface { type CASClient interface { CASUploader CASDownloader - Configured() bool } -func NewCASClientUseCase(credsProvider *CASCredentialsUseCase, config *conf.Bootstrap_CASServer, l log.Logger) *CASClientUseCase { - return &CASClientUseCase{credsProvider, config, servicelogger.ScopedHelper(l, "biz/cas-client")} +type CASClientFactory func(conf *conf.Bootstrap_CASServer, token string) (casclient.DownloaderUploader, error) +type CASClientOpts func(u *CASClientUseCase) + +func WithClientFactory(f CASClientFactory) CASClientOpts { + return func(c *CASClientUseCase) { + c.casClientFactory = f + } +} + +func NewCASClientUseCase(credsProvider *CASCredentialsUseCase, config *conf.Bootstrap_CASServer, l log.Logger, opts ...CASClientOpts) *CASClientUseCase { + // generate a client from the given configuration + defaultCasClientFactory := func(conf *conf.Bootstrap_CASServer, token string) (casclient.DownloaderUploader, error) { + conn, err := grpcconn.New(conf.GetGrpc().GetAddr(), token, conf.GetInsecure()) + if err != nil { + return nil, fmt.Errorf("failed to create grpc connection: %w", err) + } + + return casclient.New(conn), nil + } + + uc := &CASClientUseCase{ + credsProvider: credsProvider, + casServerConf: config, + logger: servicelogger.ScopedHelper(l, "biz/cas-client"), + casClientFactory: defaultCasClientFactory, + } + + for _, opt := range opts { + opt(uc) + } + + return uc } // The secretID is embedded in the JWT token and is used to identify the secret by the CAS server @@ -90,35 +124,31 @@ func (uc *CASClientUseCase) Download(ctx context.Context, secretID string, w io. } // create a client with a temporary set of credentials for a specific operation -func (uc *CASClientUseCase) casAPIClient(secretID string, role casJWT.Role) (*casclient.Client, error) { +func (uc *CASClientUseCase) casAPIClient(secretID string, role casJWT.Role) (casclient.DownloaderUploader, error) { token, err := uc.credsProvider.GenerateTemporaryCredentials(secretID, role) if err != nil { return nil, fmt.Errorf("failed to generate temporary credentials: %w", err) } // Initialize connection to CAS server - return casClient(uc.casServerConf, token) + return uc.casClientFactory(uc.casServerConf, token) } -func casClient(conf *conf.Bootstrap_CASServer, token string) (*casclient.Client, error) { - conn, err := grpcconn.New(conf.GetGrpc().GetAddr(), token, conf.GetInsecure()) - if err != nil { - return nil, fmt.Errorf("failed to create grpc connection: %w", err) - } - - return casclient.New(conn), nil -} - -// If the CAS client configuration is present and valid -func (uc *CASClientUseCase) Configured() bool { +// If the CAS server can be reached and reports readiness +func (uc *CASClientUseCase) IsReady(ctx context.Context) (bool, error) { if uc.casServerConf == nil { - return false + return false, errors.New("missing CAS server configuration") } err := uc.casServerConf.ValidateAll() if err != nil { - uc.logger.Infow("msg", "Invalid CAS client configuration", "err", err.Error()) + return false, fmt.Errorf("invalid CAS client configuration: %w", err) + } + + c, err := uc.casClientFactory(uc.casServerConf, "") + if err != nil { + return false, fmt.Errorf("failed to create CAS client: %w", err) } - return err == nil + return c.IsReady(ctx) } diff --git a/app/controlplane/internal/biz/casclient_test.go b/app/controlplane/internal/biz/casclient_test.go new file mode 100644 index 000000000..c3a2a8959 --- /dev/null +++ b/app/controlplane/internal/biz/casclient_test.go @@ -0,0 +1,84 @@ +// +// Copyright 2023 The Chainloop Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package biz_test + +import ( + "context" + "testing" + + "github.com/chainloop-dev/chainloop/app/controlplane/internal/biz" + "github.com/chainloop-dev/chainloop/app/controlplane/internal/conf" + "github.com/chainloop-dev/chainloop/internal/casclient" + "github.com/chainloop-dev/chainloop/internal/casclient/mocks" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestIsReady(t *testing.T) { + validConf := &conf.Bootstrap_CASServer{ + Grpc: &conf.Server_GRPC{Addr: "localhost:1111"}, + } + + testCases := []struct { + name string + config *conf.Bootstrap_CASServer + casReady bool + want bool + wantErr bool + }{ + { + name: "missing configuration", + config: &conf.Bootstrap_CASServer{}, + wantErr: true, + }, + { + name: "invalid configuration", + config: &conf.Bootstrap_CASServer{Grpc: &conf.Server_GRPC{}}, + wantErr: true, + }, + { + name: "not ready configuration", + config: validConf, + wantErr: false, + }, + { + name: "ready configuration", + config: validConf, + casReady: true, + want: true, + wantErr: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + clientProvider := func(conf *conf.Bootstrap_CASServer, token string) (casclient.DownloaderUploader, error) { + c := mocks.NewDownloaderUploader(t) + c.On("IsReady", mock.Anything).Return(tc.casReady, nil) + return c, nil + } + uc := biz.NewCASClientUseCase(nil, tc.config, nil, biz.WithClientFactory(clientProvider)) + + got, err := uc.IsReady(context.Background()) + if tc.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tc.want, got) + }) + } +} diff --git a/app/controlplane/internal/biz/testhelpers/wire.go b/app/controlplane/internal/biz/testhelpers/wire.go index ef3451de8..dc01c9e21 100644 --- a/app/controlplane/internal/biz/testhelpers/wire.go +++ b/app/controlplane/internal/biz/testhelpers/wire.go @@ -44,6 +44,7 @@ func WireTestData(*TestDatabase, *testing.T, log.Logger, credentials.ReaderWrite wire.Bind(new(backend.Provider), new(*oci.BackendProvider)), wire.Bind(new(credentials.Reader), new(credentials.ReaderWriter)), wire.Bind(new(biz.CASClient), new(*biz.CASClientUseCase)), + wire.Value([]biz.CASClientOpts{}), oci.NewBackendProvider, wire.Struct(new(TestingUseCases), "*"), newConfData, diff --git a/app/controlplane/internal/biz/testhelpers/wire_gen.go b/app/controlplane/internal/biz/testhelpers/wire_gen.go index 0d13cae48..6bf5ad1f3 100644 --- a/app/controlplane/internal/biz/testhelpers/wire_gen.go +++ b/app/controlplane/internal/biz/testhelpers/wire_gen.go @@ -74,7 +74,8 @@ func WireTestData(testDatabase *TestDatabase, t *testing.T, logger log.Logger, r return nil, nil, err } bootstrap_CASServer := newConfCAS() - casClientUseCase := biz.NewCASClientUseCase(casCredentialsUseCase, bootstrap_CASServer, logger) + v := _wireValue + casClientUseCase := biz.NewCASClientUseCase(casCredentialsUseCase, bootstrap_CASServer, logger, v...) integration := dependencytrack.New(integrationUseCase, ociRepositoryUseCase, readerWriter, casClientUseCase, logger) testingUseCases := &TestingUseCases{ DB: testDatabase, @@ -94,3 +95,7 @@ func WireTestData(testDatabase *TestDatabase, t *testing.T, logger log.Logger, r cleanup() }, nil } + +var ( + _wireValue = []biz.CASClientOpts{} +) diff --git a/app/controlplane/internal/server/grpc.go b/app/controlplane/internal/server/grpc.go index 61c0a8de5..e398f3b38 100644 --- a/app/controlplane/internal/server/grpc.go +++ b/app/controlplane/internal/server/grpc.go @@ -47,6 +47,7 @@ type Opts struct { UserUseCase *biz.UserUseCase RobotAccountUseCase *biz.RobotAccountUseCase OCIRepositoryUseCase *biz.OCIRepositoryUseCase + CASClientUseCase *biz.CASClientUseCase // Services WorkflowSvc *service.WorkflowService AuthSvc *service.AuthService @@ -85,7 +86,7 @@ func NewGRPCServer(opts *Opts) *grpc.Server { srv := grpc.NewServer(serverOpts...) v1.RegisterWorkflowServiceServer(srv, opts.WorkflowSvc) - v1.RegisterStatusServiceServer(srv, service.NewStatusService(opts.AuthSvc.AuthURLs.Login, Version)) + v1.RegisterStatusServiceServer(srv, service.NewStatusService(opts.AuthSvc.AuthURLs.Login, Version, opts.CASClientUseCase)) v1.RegisterRobotAccountServiceServer(srv, opts.RobotAccountSvc) v1.RegisterWorkflowRunServiceServer(srv, opts.WorkflowRunSvc) v1.RegisterAttestationServiceServer(srv, opts.AttesstationSvc) diff --git a/app/controlplane/internal/server/http.go b/app/controlplane/internal/server/http.go index 26e46077e..13389b9de 100644 --- a/app/controlplane/internal/server/http.go +++ b/app/controlplane/internal/server/http.go @@ -47,7 +47,7 @@ func NewHTTPServer(opts *Opts, grpcSrv *grpc.Server) (*http.Server, error) { // NOTE: these non-grpc transcoded methods DO NOT RUN the middlewares httpSrv.Handle(service.AuthLoginPath, opts.AuthSvc.RegisterLoginHandler()) httpSrv.Handle(service.AuthCallbackPath, opts.AuthSvc.RegisterCallbackHandler()) - v1.RegisterStatusServiceHTTPServer(httpSrv, service.NewStatusService(opts.AuthSvc.AuthURLs.Login, Version)) + v1.RegisterStatusServiceHTTPServer(httpSrv, service.NewStatusService(opts.AuthSvc.AuthURLs.Login, Version, opts.CASClientUseCase)) // Wrap http server to handle grpc-web calls and we will return this new server wrappedServer := http.NewServer(serverOpts...) diff --git a/app/controlplane/internal/service/status.go b/app/controlplane/internal/service/status.go index 867604095..e9e6c79a6 100644 --- a/app/controlplane/internal/service/status.go +++ b/app/controlplane/internal/service/status.go @@ -19,18 +19,27 @@ import ( "context" pb "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1" + "github.com/chainloop-dev/chainloop/app/controlplane/internal/biz" + "github.com/go-kratos/kratos/v2/errors" ) type StatusService struct { loginURL, version string pb.UnimplementedStatusServiceServer + casClient *biz.CASClientUseCase } -func NewStatusService(logingURL, version string) *StatusService { - return &StatusService{loginURL: logingURL, version: version} +func NewStatusService(logingURL, version string, casClient *biz.CASClientUseCase) *StatusService { + return &StatusService{loginURL: logingURL, version: version, casClient: casClient} } -func (s *StatusService) Statusz(_ context.Context, _ *pb.StatuszRequest) (*pb.StatuszResponse, error) { +// Only on readiness probes we check this service external dependencies +func (s *StatusService) Statusz(ctx context.Context, r *pb.StatuszRequest) (*pb.StatuszResponse, error) { + if r.Readiness { + if ok, err := s.casClient.IsReady(ctx); err != nil || !ok { + return nil, errors.ServiceUnavailable("CAS_NOT_READY", "Artifact CAS is not reachable") + } + } return &pb.StatuszResponse{}, nil } diff --git a/internal/casclient/casclient.go b/internal/casclient/casclient.go index 2ada80f83..44657d319 100644 --- a/internal/casclient/casclient.go +++ b/internal/casclient/casclient.go @@ -37,10 +37,14 @@ type ResourceInfo struct { type Uploader interface { UploadFile(ctx context.Context, filepath string) (*UpDownStatus, error) Upload(ctx context.Context, r io.Reader, digest, fileName string) (*UpDownStatus, error) + // Whether the CAS is ready to accept uploads + IsReady(ctx context.Context) (bool, error) } type Downloader interface { Download(ctx context.Context, w io.Writer, digest string) error + // Whether the CAS is ready to accept downloads + IsReady(ctx context.Context) (bool, error) } type DownloaderUploader interface { diff --git a/internal/casclient/downloader.go b/internal/casclient/downloader.go index 1e8648046..93021812e 100644 --- a/internal/casclient/downloader.go +++ b/internal/casclient/downloader.go @@ -95,3 +95,14 @@ func (c *Client) Describe(ctx context.Context, digest string) (*ResourceInfo, er Digest: resp.GetResult().GetDigest(), Filename: resp.Result.GetFileName(), Size: resp.Result.GetSize(), }, nil } + +// Contact the API to check if the service is ready to accept connections +func (c *Client) IsReady(ctx context.Context) (bool, error) { + client := v1.NewStatusServiceClient(c.conn) + _, err := client.Statusz(ctx, &v1.StatuszRequest{Readiness: true}) + if err != nil { + return false, fmt.Errorf("contacting API to get status: %w", err) + } + + return true, nil +} diff --git a/internal/casclient/mocks/Downloader.go b/internal/casclient/mocks/Downloader.go index a838502c1..04a1ff680 100644 --- a/internal/casclient/mocks/Downloader.go +++ b/internal/casclient/mocks/Downloader.go @@ -28,6 +28,30 @@ func (_m *Downloader) Download(ctx context.Context, w io.Writer, digest string) return r0 } +// IsReady provides a mock function with given fields: ctx +func (_m *Downloader) IsReady(ctx context.Context) (bool, error) { + ret := _m.Called(ctx) + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (bool, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) bool); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + type mockConstructorTestingTNewDownloader interface { mock.TestingT Cleanup(func()) diff --git a/internal/casclient/mocks/DownloaderUploader.go b/internal/casclient/mocks/DownloaderUploader.go index 09fd0b85c..3ba6de49d 100644 --- a/internal/casclient/mocks/DownloaderUploader.go +++ b/internal/casclient/mocks/DownloaderUploader.go @@ -31,6 +31,30 @@ func (_m *DownloaderUploader) Download(ctx context.Context, w io.Writer, digest return r0 } +// IsReady provides a mock function with given fields: ctx +func (_m *DownloaderUploader) IsReady(ctx context.Context) (bool, error) { + ret := _m.Called(ctx) + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (bool, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) bool); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // Upload provides a mock function with given fields: ctx, r, digest, fileName func (_m *DownloaderUploader) Upload(ctx context.Context, r io.Reader, digest string, fileName string) (*casclient.UpDownStatus, error) { ret := _m.Called(ctx, r, digest, fileName) diff --git a/internal/casclient/mocks/Uploader.go b/internal/casclient/mocks/Uploader.go index 7e711c615..7f4b9ef0b 100644 --- a/internal/casclient/mocks/Uploader.go +++ b/internal/casclient/mocks/Uploader.go @@ -17,6 +17,30 @@ type Uploader struct { mock.Mock } +// IsReady provides a mock function with given fields: ctx +func (_m *Uploader) IsReady(ctx context.Context) (bool, error) { + ret := _m.Called(ctx) + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (bool, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) bool); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // Upload provides a mock function with given fields: ctx, r, digest, fileName func (_m *Uploader) Upload(ctx context.Context, r io.Reader, digest string, fileName string) (*casclient.UpDownStatus, error) { ret := _m.Called(ctx, r, digest, fileName)