dragonfly/pkg/objectstorage/s3.go

245 lines
7.1 KiB
Go

/*
* Copyright 2022 The Dragonfly 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 objectstorage
import (
"context"
"fmt"
"io"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session"
awss3 "github.com/aws/aws-sdk-go/service/s3"
)
type s3 struct {
// S3 client.
client *awss3.S3
}
// New s3 instance.
func newS3(region, endpoint, accessKey, secretKey string, s3ForcePathStyle bool) (ObjectStorage, error) {
cfg := aws.NewConfig().WithCredentials(credentials.NewStaticCredentials(accessKey, secretKey, ""))
s, err := session.NewSession(cfg)
if err != nil {
return nil, fmt.Errorf("new aws session failed: %s", err)
}
return &s3{
client: awss3.New(s, cfg.WithRegion(region), cfg.WithEndpoint(endpoint), cfg.WithS3ForcePathStyle(s3ForcePathStyle)),
}, nil
}
// GetBucketMetadata returns metadata of bucket.
func (s *s3) GetBucketMetadata(ctx context.Context, bucketName string) (*BucketMetadata, error) {
_, err := s.client.HeadBucketWithContext(ctx, &awss3.HeadBucketInput{Bucket: aws.String(bucketName)})
if err != nil {
return nil, err
}
return &BucketMetadata{
Name: bucketName,
}, nil
}
// CreateBucket creates bucket of object storage.
func (s *s3) CreateBucket(ctx context.Context, bucketName string) error {
_, err := s.client.CreateBucketWithContext(ctx, &awss3.CreateBucketInput{Bucket: aws.String(bucketName)})
return err
}
// DeleteBucket deletes bucket of object storage.
func (s *s3) DeleteBucket(ctx context.Context, bucketName string) error {
_, err := s.client.DeleteBucketWithContext(ctx, &awss3.DeleteBucketInput{Bucket: aws.String(bucketName)})
return err
}
// DeleteBucket deletes bucket of object storage.
func (s *s3) ListBucketMetadatas(ctx context.Context) ([]*BucketMetadata, error) {
resp, err := s.client.ListBucketsWithContext(ctx, &awss3.ListBucketsInput{})
if err != nil {
return nil, err
}
var metadatas []*BucketMetadata
for _, bucket := range resp.Buckets {
metadatas = append(metadatas, &BucketMetadata{
Name: aws.StringValue(bucket.Name),
CreateAt: aws.TimeValue(bucket.CreationDate),
})
}
return metadatas, nil
}
// GetObjectMetadata returns metadata of object.
func (s *s3) GetObjectMetadata(ctx context.Context, bucketName, objectKey string) (*ObjectMetadata, bool, error) {
resp, err := s.client.HeadObjectWithContext(ctx, &awss3.HeadObjectInput{
Bucket: aws.String(bucketName),
Key: aws.String(objectKey),
})
if err != nil {
// S3 is missing this error code.
if aerr, ok := err.(awserr.Error); ok && aerr.Code() == "NotFound" {
return nil, false, nil
}
return nil, false, err
}
return &ObjectMetadata{
Key: objectKey,
ContentDisposition: aws.StringValue(resp.ContentDisposition),
ContentEncoding: aws.StringValue(resp.ContentEncoding),
ContentLanguage: aws.StringValue(resp.ContentLanguage),
ContentLength: aws.Int64Value(resp.ContentLength),
ContentType: aws.StringValue(resp.ContentType),
ETag: aws.StringValue(resp.ETag),
Digest: aws.StringValue(resp.Metadata[MetaDigest]),
}, true, nil
}
// GetOject returns data of object.
func (s *s3) GetOject(ctx context.Context, bucketName, objectKey string) (io.ReadCloser, error) {
resp, err := s.client.GetObjectWithContext(ctx, &awss3.GetObjectInput{
Bucket: aws.String(bucketName),
Key: aws.String(objectKey),
})
if err != nil {
return nil, err
}
return resp.Body, nil
}
// PutObject puts data of object.
func (s *s3) PutObject(ctx context.Context, bucketName, objectKey, digest string, reader io.Reader) error {
meta := map[string]string{}
meta[MetaDigest] = digest
_, err := s.client.PutObjectWithContext(ctx, &awss3.PutObjectInput{
Bucket: aws.String(bucketName),
Key: aws.String(objectKey),
Body: aws.ReadSeekCloser(reader),
Metadata: aws.StringMap(meta),
})
return err
}
// DeleteObject deletes data of object.
func (s *s3) DeleteObject(ctx context.Context, bucketName, objectKey string) error {
_, err := s.client.DeleteObjectWithContext(ctx, &awss3.DeleteObjectInput{
Bucket: aws.String(bucketName),
Key: aws.String(objectKey),
})
return err
}
// DeleteObject deletes data of object.
func (s *s3) ListObjectMetadatas(ctx context.Context, bucketName, prefix, marker string, limit int64) ([]*ObjectMetadata, error) {
resp, err := s.client.ListObjectsWithContext(ctx, &awss3.ListObjectsInput{
Bucket: aws.String(bucketName),
Prefix: aws.String(prefix),
Marker: aws.String(marker),
MaxKeys: aws.Int64(limit),
})
if err != nil {
return nil, err
}
var metadatas []*ObjectMetadata
for _, object := range resp.Contents {
metadatas = append(metadatas, &ObjectMetadata{
Key: aws.StringValue(object.Key),
ETag: aws.StringValue(object.ETag),
})
}
return metadatas, nil
}
// IsObjectExist returns whether the object exists.
func (s *s3) IsObjectExist(ctx context.Context, bucketName, objectKey string) (bool, error) {
_, isExist, err := s.GetObjectMetadata(ctx, bucketName, objectKey)
if err != nil {
return false, err
}
if !isExist {
return false, nil
}
return true, nil
}
// IsBucketExist returns whether the bucket exists.
func (s *s3) IsBucketExist(ctx context.Context, bucketName string) (bool, error) {
if _, err := s.client.HeadBucketWithContext(ctx, &awss3.HeadBucketInput{
Bucket: &bucketName,
}); err != nil {
if aerr, ok := err.(awserr.Error); ok && aerr.Code() == awss3.ErrCodeNoSuchBucket {
return false, nil
}
return false, err
}
return true, nil
}
// GetSignURL returns sign url of object.
func (s *s3) GetSignURL(ctx context.Context, bucketName, objectKey string, method Method, expire time.Duration) (string, error) {
var req *request.Request
switch method {
case MethodGet:
req, _ = s.client.GetObjectRequest(&awss3.GetObjectInput{
Bucket: aws.String(bucketName),
Key: aws.String(objectKey),
})
case MethodPut:
req, _ = s.client.PutObjectRequest(&awss3.PutObjectInput{
Bucket: aws.String(bucketName),
Key: aws.String(objectKey),
})
case MethodHead:
req, _ = s.client.HeadObjectRequest(&awss3.HeadObjectInput{
Bucket: aws.String(bucketName),
Key: aws.String(objectKey),
})
case MethodDelete:
req, _ = s.client.DeleteObjectRequest(&awss3.DeleteObjectInput{
Bucket: aws.String(bucketName),
Key: aws.String(objectKey),
})
case MethodList:
req, _ = s.client.ListObjectsRequest(&awss3.ListObjectsInput{
Bucket: aws.String(bucketName),
})
default:
return "", fmt.Errorf("not support method %s", method)
}
return req.Presign(expire)
}