148 lines
3.9 KiB
Go
148 lines
3.9 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/aws/aws-sdk-go-v2/aws"
|
|
awsconfig "github.com/aws/aws-sdk-go-v2/config"
|
|
"github.com/aws/aws-sdk-go-v2/credentials"
|
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
|
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
|
"github.com/perfect-panel/server/internal/config"
|
|
)
|
|
|
|
type PresignUploadResult struct {
|
|
URL string
|
|
Method string
|
|
ExpiresAt int64
|
|
}
|
|
|
|
type ObjectMeta struct {
|
|
ETag string
|
|
ContentType string
|
|
ContentLength int64
|
|
}
|
|
|
|
type PutObjectResult struct {
|
|
ETag string
|
|
}
|
|
|
|
type S3Store struct {
|
|
cfg config.S3Config
|
|
client *s3.Client
|
|
presign *s3.PresignClient
|
|
}
|
|
|
|
func NewS3Store(ctx context.Context, cfg config.S3Config) (*S3Store, error) {
|
|
loaders := make([]func(*awsconfig.LoadOptions) error, 0, 3)
|
|
if cfg.Region != "" {
|
|
loaders = append(loaders, awsconfig.WithRegion(cfg.Region))
|
|
}
|
|
if cfg.AccessKey != "" || cfg.SecretKey != "" || cfg.SessionToken != "" {
|
|
loaders = append(loaders, awsconfig.WithCredentialsProvider(
|
|
credentials.NewStaticCredentialsProvider(cfg.AccessKey, cfg.SecretKey, cfg.SessionToken),
|
|
))
|
|
}
|
|
|
|
awsCfg, err := awsconfig.LoadDefaultConfig(ctx, loaders...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
client := s3.NewFromConfig(awsCfg, func(o *s3.Options) {
|
|
o.UsePathStyle = cfg.UsePathStyle
|
|
if cfg.Endpoint != "" {
|
|
o.BaseEndpoint = aws.String(cfg.Endpoint)
|
|
}
|
|
})
|
|
|
|
return &S3Store{
|
|
cfg: cfg,
|
|
client: client,
|
|
presign: s3.NewPresignClient(client),
|
|
}, nil
|
|
}
|
|
|
|
func (s *S3Store) PresignPutObject(ctx context.Context, objectKey string, contentType string, expires time.Duration) (*PresignUploadResult, error) {
|
|
req, err := s.presign.PresignPutObject(ctx, &s3.PutObjectInput{
|
|
Bucket: aws.String(s.cfg.Bucket),
|
|
Key: aws.String(objectKey),
|
|
ContentType: aws.String(contentType),
|
|
}, s3.WithPresignExpires(expires))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &PresignUploadResult{
|
|
URL: req.URL,
|
|
Method: req.Method,
|
|
ExpiresAt: time.Now().Add(expires).Unix(),
|
|
}, nil
|
|
}
|
|
|
|
func (s *S3Store) HeadObject(ctx context.Context, objectKey string) (*ObjectMeta, error) {
|
|
resp, err := s.client.HeadObject(ctx, &s3.HeadObjectInput{
|
|
Bucket: aws.String(s.cfg.Bucket),
|
|
Key: aws.String(objectKey),
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &ObjectMeta{
|
|
ETag: strings.Trim(aws.ToString(resp.ETag), "\""),
|
|
ContentType: aws.ToString(resp.ContentType),
|
|
ContentLength: aws.ToInt64(resp.ContentLength),
|
|
}, nil
|
|
}
|
|
|
|
func (s *S3Store) PutObject(ctx context.Context, objectKey string, body io.Reader, size int64, contentType string) (*PutObjectResult, error) {
|
|
resp, err := s.client.PutObject(ctx, &s3.PutObjectInput{
|
|
Bucket: aws.String(s.cfg.Bucket),
|
|
Key: aws.String(objectKey),
|
|
Body: body,
|
|
ContentLength: aws.Int64(size),
|
|
ContentType: aws.String(contentType),
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &PutObjectResult{ETag: strings.Trim(aws.ToString(resp.ETag), "\"")}, nil
|
|
}
|
|
|
|
func (s *S3Store) BuildObjectURL(objectKey string) string {
|
|
if s.cfg.PublicBaseURL != "" {
|
|
return strings.TrimRight(s.cfg.PublicBaseURL, "/") + "/" + strings.TrimLeft(objectKey, "/")
|
|
}
|
|
|
|
if s.cfg.Endpoint != "" {
|
|
base := strings.TrimRight(s.cfg.Endpoint, "/")
|
|
return fmt.Sprintf("%s/%s/%s", base, url.PathEscape(s.cfg.Bucket), strings.TrimLeft(objectKey, "/"))
|
|
}
|
|
|
|
return fmt.Sprintf("https://%s.s3.%s.amazonaws.com/%s", s.cfg.Bucket, s.cfg.Region, strings.TrimLeft(objectKey, "/"))
|
|
}
|
|
|
|
func (s *S3Store) PutJSONTagging(ctx context.Context, objectKey string, values map[string]string) error {
|
|
tagging := make([]types.Tag, 0, len(values))
|
|
for k, v := range values {
|
|
key := k
|
|
value := v
|
|
tagging = append(tagging, types.Tag{Key: &key, Value: &value})
|
|
}
|
|
|
|
_, err := s.client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{
|
|
Bucket: aws.String(s.cfg.Bucket),
|
|
Key: aws.String(objectKey),
|
|
Tagging: &types.Tagging{
|
|
TagSet: tagging,
|
|
},
|
|
})
|
|
return err
|
|
}
|