Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/content-service/pkg/service/blob-service.go
2499 views
1
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package service
6
7
import (
8
"context"
9
"strings"
10
11
"github.com/opentracing/opentracing-go"
12
"google.golang.org/grpc/codes"
13
"google.golang.org/grpc/status"
14
15
"github.com/gitpod-io/gitpod/common-go/log"
16
"github.com/gitpod-io/gitpod/common-go/tracing"
17
"github.com/gitpod-io/gitpod/content-service/api"
18
config "github.com/gitpod-io/gitpod/content-service/api/config"
19
"github.com/gitpod-io/gitpod/content-service/pkg/storage"
20
)
21
22
// BlobService implements BlobServiceServer
23
type BlobService struct {
24
cfg config.StorageConfig
25
s storage.PresignedAccess
26
27
api.UnimplementedBlobServiceServer
28
}
29
30
// NewBlobService create a new content service
31
func NewBlobService(cfg config.StorageConfig) (res *BlobService, err error) {
32
s, err := storage.NewPresignedAccess(&cfg)
33
if err != nil {
34
return nil, err
35
}
36
return &BlobService{cfg: cfg, s: s}, nil
37
}
38
39
// UploadUrl provides a upload URL
40
func (cs *BlobService) UploadUrl(ctx context.Context, req *api.UploadUrlRequest) (resp *api.UploadUrlResponse, err error) {
41
//nolint:ineffassign
42
span, ctx := opentracing.StartSpanFromContext(ctx, "UploadUrl")
43
span.SetTag("user", req.OwnerId)
44
span.SetTag("name", req.Name)
45
defer tracing.FinishSpan(span, &err)
46
47
bucket := cs.s.Bucket(req.OwnerId)
48
49
err = cs.s.EnsureExists(ctx, bucket)
50
if err != nil {
51
return nil, status.Error(codes.NotFound, err.Error())
52
}
53
54
blobName, err := cs.s.BlobObject(req.OwnerId, req.Name)
55
if err != nil {
56
return nil, status.Error(codes.InvalidArgument, err.Error())
57
}
58
59
if cs.cfg.BlobQuota > 0 {
60
prefix := strings.Split(blobName, "/")[0]
61
size, err := cs.s.DiskUsage(ctx, bucket, prefix)
62
63
if err != nil {
64
return nil, status.Error(codes.Unknown, err.Error())
65
}
66
exceeded := size >= cs.cfg.BlobQuota
67
log.WithFields(log.OWI(req.OwnerId, "", "")).Debugf("checking blob quota - quota: %d, size: %d, exceeded: %t", cs.cfg.BlobQuota, size, exceeded)
68
if exceeded {
69
return nil, status.Error(codes.ResourceExhausted, "quota exceeded")
70
}
71
} else {
72
log.Debug("blob quota disabled")
73
}
74
75
info, err := cs.s.SignUpload(ctx, bucket, blobName, &storage.SignedURLOptions{
76
ContentType: req.ContentType,
77
})
78
if err != nil {
79
log.Error("error getting SignUpload URL: ", err)
80
if err == storage.ErrNotFound {
81
return nil, status.Error(codes.NotFound, err.Error())
82
}
83
return nil, status.Error(codes.Unknown, err.Error())
84
}
85
86
return &api.UploadUrlResponse{
87
Url: info.URL,
88
}, nil
89
}
90
91
// DownloadUrl provides a download URL
92
func (cs *BlobService) DownloadUrl(ctx context.Context, req *api.DownloadUrlRequest) (resp *api.DownloadUrlResponse, err error) {
93
//nolint:ineffassign
94
span, ctx := opentracing.StartSpanFromContext(ctx, "DownloadUrl")
95
span.SetTag("user", req.OwnerId)
96
span.SetTag("name", req.Name)
97
defer tracing.FinishSpan(span, &err)
98
99
blobName, err := cs.s.BlobObject(req.OwnerId, req.Name)
100
if err != nil {
101
return nil, status.Error(codes.InvalidArgument, err.Error())
102
}
103
104
info, err := cs.s.SignDownload(ctx, cs.s.Bucket(req.OwnerId), blobName, &storage.SignedURLOptions{
105
ContentType: req.ContentType,
106
})
107
if err != nil {
108
log.Error("error getting SignDownload URL: ", err)
109
if err == storage.ErrNotFound {
110
return nil, status.Error(codes.NotFound, err.Error())
111
}
112
return nil, status.Error(codes.Unknown, err.Error())
113
}
114
115
return &api.DownloadUrlResponse{
116
Url: info.URL,
117
}, nil
118
}
119
120
// Delete deletes the uploaded content
121
func (cs *BlobService) Delete(ctx context.Context, req *api.DeleteRequest) (resp *api.DeleteResponse, err error) {
122
//nolint:ineffassign
123
span, ctx := opentracing.StartSpanFromContext(ctx, "Delete")
124
span.SetTag("user", req.OwnerId)
125
span.SetTag("name", req.Name)
126
defer tracing.FinishSpan(span, &err)
127
128
var query *storage.DeleteObjectQuery
129
exact := req.GetExact()
130
prefix := req.GetPrefix()
131
if exact != "" {
132
exact, err = cs.s.BlobObject(req.OwnerId, exact)
133
if err != nil {
134
return nil, status.Error(codes.InvalidArgument, err.Error())
135
}
136
query = &storage.DeleteObjectQuery{Name: exact}
137
} else if prefix != "" {
138
prefix, err = cs.s.BlobObject(req.OwnerId, prefix)
139
if err != nil {
140
return nil, status.Error(codes.InvalidArgument, err.Error())
141
}
142
query = &storage.DeleteObjectQuery{Prefix: prefix}
143
} else {
144
return nil, status.Error(codes.InvalidArgument, "Name arg is missing")
145
}
146
147
bucket := cs.s.Bucket(req.OwnerId)
148
149
err = cs.s.DeleteObject(ctx, bucket, query)
150
if err == storage.ErrNotFound {
151
return nil, status.Error(codes.NotFound, err.Error())
152
}
153
if err != nil {
154
return nil, status.Error(codes.Unknown, err.Error())
155
}
156
resp = &api.DeleteResponse{}
157
return resp, nil
158
}
159
160