diff --git a/components/content-service/pkg/service/usage-report-service.go b/components/content-service/pkg/service/usage-report-service.go index 81ddc35c5397e5..c2820d0877f542 100644 --- a/components/content-service/pkg/service/usage-report-service.go +++ b/components/content-service/pkg/service/usage-report-service.go @@ -6,7 +6,7 @@ package service import ( "context" - + "errors" "github.com/opentracing/opentracing-go" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -42,20 +42,64 @@ func (us *UsageReportService) UploadURL(ctx context.Context, req *api.UsageRepor span.SetTag("name", req.Name) defer tracing.FinishSpan(span, &err) - err = us.s.EnsureExists(ctx, us.bucketName) + if req.GetName() == "" { + return nil, status.Error(codes.InvalidArgument, "Name is required but got empty.") + } + + logger := log.WithField("name", req.Name). + WithField("bucket", us.bucketName) + + err = us.ensureBucketExists(ctx) if err != nil { - return nil, status.Error(codes.NotFound, err.Error()) + return nil, err } info, err := us.s.SignUpload(ctx, us.bucketName, req.Name, &storage.SignedURLOptions{ ContentType: "application/json", }) if err != nil { - log.WithField("name", req.Name). - WithField("bucket", us.bucketName). - WithError(err). - Error("Error getting UsageReport SignUpload URL") - return nil, status.Error(codes.Unknown, err.Error()) + logger.WithError(err).Error("Error getting UsageReport SignUpload URL") + return nil, status.Error(codes.Internal, err.Error()) } return &api.UsageReportUploadURLResponse{Url: info.URL}, nil } + +func (us *UsageReportService) DownloadURL(ctx context.Context, req *api.UsageReportDownloadURLRequest) (resp *api.UsageReportDownloadURLResponse, err error) { + span, ctx := opentracing.StartSpanFromContext(ctx, "UsageReport.DownloadURL") + span.SetTag("name", req.Name) + defer tracing.FinishSpan(span, &err) + + if req.GetName() == "" { + return nil, status.Error(codes.InvalidArgument, "Name is required but got empty.") + } + + err = us.ensureBucketExists(ctx) + if err != nil { + return nil, err + } + + download, err := us.s.SignDownload(ctx, us.bucketName, req.GetName(), &storage.SignedURLOptions{ + ContentType: "application/json", + }) + if err != nil { + if errors.Is(err, storage.ErrNotFound) { + return nil, status.Errorf(codes.NotFound, "Object %s does not exist.", req.GetName()) + } + + return nil, status.Errorf(codes.Internal, "Failed to generate download URL for usage report: %s", err.Error()) + } + + return &api.UsageReportDownloadURLResponse{ + Url: download.URL, + }, nil +} + +func (us *UsageReportService) ensureBucketExists(ctx context.Context) error { + err := us.s.EnsureExists(ctx, us.bucketName) + if err != nil { + log.WithError(err).Errorf("Bucket %s does not exist", us.bucketName) + return status.Error(codes.Internal, err.Error()) + } + + return nil +} diff --git a/components/content-service/pkg/service/usage-report-service_test.go b/components/content-service/pkg/service/usage-report-service_test.go index 4a87758366383c..a8e34a1a170f8f 100644 --- a/components/content-service/pkg/service/usage-report-service_test.go +++ b/components/content-service/pkg/service/usage-report-service_test.go @@ -38,3 +38,24 @@ func TestUploadURL(t *testing.T) { require.NoError(t, err) require.Equal(t, "http://example.com/some-path", resp.Url) } + +func TestDownloadURL(t *testing.T) { + const ( + fileName = "some-report-filename" + bucketName = "gitpod-usage-reports" + ) + + ctrl := gomock.NewController(t) + s := storagemock.NewMockPresignedAccess(ctrl) + + s.EXPECT().EnsureExists(gomock.Any(), bucketName). + Return(nil) + s.EXPECT().SignDownload(gomock.Any(), bucketName, fileName, gomock.Any()). + Return(&storage.DownloadInfo{URL: "http://example.com/some-path"}, nil) + + svc := &UsageReportService{cfg: config.StorageConfig{}, s: s, bucketName: bucketName} + resp, err := svc.DownloadURL(context.Background(), &api.UsageReportDownloadURLRequest{Name: fileName}) + + require.NoError(t, err) + require.Equal(t, "http://example.com/some-path", resp.Url) +} diff --git a/components/content-service/pkg/storage/storage.go b/components/content-service/pkg/storage/storage.go index 5a92a9c67d2c4e..dd1e5eb8821065 100644 --- a/components/content-service/pkg/storage/storage.go +++ b/components/content-service/pkg/storage/storage.go @@ -32,7 +32,7 @@ const ( var ( // ErrNotFound is returned when an object is not found - ErrNotFound = xerrors.Errorf("not found") + ErrNotFound = fmt.Errorf("not found") ) // BucketNamer provides names for storage buckets