114 lines
3.2 KiB
Go
114 lines
3.2 KiB
Go
|
|
package services
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"context"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"net/url"
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
"github.com/minio/minio-go/v7"
|
||
|
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||
|
|
)
|
||
|
|
|
||
|
|
type s3StorageBackend struct {
|
||
|
|
cfg StorageBackendConfig
|
||
|
|
client *minio.Client
|
||
|
|
}
|
||
|
|
|
||
|
|
func newS3StorageBackend(cfg StorageBackendConfig) (*s3StorageBackend, error) {
|
||
|
|
endpoint := normalizeS3Endpoint(cfg.Endpoint)
|
||
|
|
client, err := minio.New(endpoint, &minio.Options{
|
||
|
|
Creds: credentials.NewStaticV4(cfg.AccessKey, cfg.SecretKey, ""),
|
||
|
|
Secure: cfg.UseSSL,
|
||
|
|
Region: cfg.Region,
|
||
|
|
BucketLookup: s3BucketLookup(cfg.PathStyle),
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return &s3StorageBackend{cfg: cfg, client: client}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b *s3StorageBackend) ID() string { return b.cfg.ID }
|
||
|
|
func (b *s3StorageBackend) Type() string { return StorageBackendS3 }
|
||
|
|
|
||
|
|
func (b *s3StorageBackend) Put(ctx context.Context, key string, body io.Reader, size int64, contentType string) error {
|
||
|
|
opts := minio.PutObjectOptions{ContentType: contentType}
|
||
|
|
_, err := b.client.PutObject(ctx, b.cfg.Bucket, cleanObjectKey(key), body, size, opts)
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b *s3StorageBackend) Get(ctx context.Context, key string) (StorageObject, error) {
|
||
|
|
object, err := b.client.GetObject(ctx, b.cfg.Bucket, cleanObjectKey(key), minio.GetObjectOptions{})
|
||
|
|
if err != nil {
|
||
|
|
return StorageObject{}, err
|
||
|
|
}
|
||
|
|
info, err := object.Stat()
|
||
|
|
if err != nil {
|
||
|
|
object.Close()
|
||
|
|
return StorageObject{}, err
|
||
|
|
}
|
||
|
|
return StorageObject{Key: key, Size: info.Size, ContentType: info.ContentType, ModTime: info.LastModified, Body: object}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b *s3StorageBackend) Delete(ctx context.Context, key string) error {
|
||
|
|
return b.client.RemoveObject(ctx, b.cfg.Bucket, cleanObjectKey(key), minio.RemoveObjectOptions{})
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b *s3StorageBackend) DeletePrefix(ctx context.Context, prefix string) error {
|
||
|
|
prefix = strings.TrimSuffix(cleanObjectKey(prefix), "/") + "/"
|
||
|
|
objects := b.client.ListObjects(ctx, b.cfg.Bucket, minio.ListObjectsOptions{Prefix: prefix, Recursive: true})
|
||
|
|
for object := range objects {
|
||
|
|
if object.Err != nil {
|
||
|
|
return object.Err
|
||
|
|
}
|
||
|
|
if err := b.Delete(ctx, object.Key); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b *s3StorageBackend) Usage(ctx context.Context) (int64, error) {
|
||
|
|
var total int64
|
||
|
|
for object := range b.client.ListObjects(ctx, b.cfg.Bucket, minio.ListObjectsOptions{Recursive: true}) {
|
||
|
|
if object.Err != nil {
|
||
|
|
return 0, object.Err
|
||
|
|
}
|
||
|
|
total += object.Size
|
||
|
|
}
|
||
|
|
return total, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b *s3StorageBackend) Test(ctx context.Context) error {
|
||
|
|
exists, err := b.client.BucketExists(ctx, b.cfg.Bucket)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if !exists {
|
||
|
|
return fmt.Errorf("bucket %q does not exist", b.cfg.Bucket)
|
||
|
|
}
|
||
|
|
key := ".warpbox-storage-test-" + randomID(6)
|
||
|
|
if err := b.Put(ctx, key, bytes.NewReader([]byte("ok")), 2, "text/plain"); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return b.Delete(ctx, key)
|
||
|
|
}
|
||
|
|
|
||
|
|
func s3BucketLookup(pathStyle bool) minio.BucketLookupType {
|
||
|
|
if pathStyle {
|
||
|
|
return minio.BucketLookupPath
|
||
|
|
}
|
||
|
|
return minio.BucketLookupAuto
|
||
|
|
}
|
||
|
|
|
||
|
|
func normalizeS3Endpoint(endpoint string) string {
|
||
|
|
endpoint = strings.TrimSpace(endpoint)
|
||
|
|
if parsed, err := url.Parse(endpoint); err == nil && parsed.Host != "" {
|
||
|
|
return parsed.Host
|
||
|
|
}
|
||
|
|
return strings.TrimPrefix(strings.TrimPrefix(endpoint, "https://"), "http://")
|
||
|
|
}
|