mirror of
https://github.com/versity/versitygw.git
synced 2025-12-23 05:05:16 +00:00
The error check for the SDK call in `GetObjectAttributes` in the S3 proxy backend was missing, which caused the gateway to panic in all cases where the SDK method returned an error. The error check has now been added so that the method returns an error when the SDK call fails.
1787 lines
56 KiB
Go
1787 lines
56 KiB
Go
// Copyright 2023 Versity Software
|
|
// This file is 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 s3proxy
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
"time"
|
|
|
|
v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
|
|
awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
|
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
|
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
|
"github.com/aws/smithy-go"
|
|
"github.com/versity/versitygw/auth"
|
|
"github.com/versity/versitygw/backend"
|
|
"github.com/versity/versitygw/s3err"
|
|
"github.com/versity/versitygw/s3response"
|
|
)
|
|
|
|
type metaPrefix string
|
|
|
|
const (
|
|
metaPrefixAcl metaPrefix = "vgw-meta-acl-"
|
|
metaPrefixPolicy metaPrefix = "vgw-meta-policy-"
|
|
metaPrefixCors metaPrefix = "vgw-meta-cors-"
|
|
)
|
|
|
|
type S3Proxy struct {
|
|
backend.BackendUnsupported
|
|
|
|
client *s3.Client
|
|
|
|
access string
|
|
secret string
|
|
endpoint string
|
|
awsRegion string
|
|
metaBucket string
|
|
disableChecksum bool
|
|
sslSkipVerify bool
|
|
usePathStyle bool
|
|
debug bool
|
|
}
|
|
|
|
var _ backend.Backend = &S3Proxy{}
|
|
|
|
func NewWithClient(ctx context.Context, client *s3.Client, metaBucket string) (*S3Proxy, error) {
|
|
s := &S3Proxy{
|
|
metaBucket: metaBucket,
|
|
}
|
|
s.client = client
|
|
return s, s.validate(ctx)
|
|
}
|
|
|
|
func New(ctx context.Context, access, secret, endpoint, region, metaBucket string, disableChecksum, sslSkipVerify, usePathStyle, debug bool) (*S3Proxy, error) {
|
|
s := &S3Proxy{
|
|
access: access,
|
|
secret: secret,
|
|
endpoint: endpoint,
|
|
awsRegion: region,
|
|
metaBucket: metaBucket,
|
|
disableChecksum: disableChecksum,
|
|
sslSkipVerify: sslSkipVerify,
|
|
usePathStyle: usePathStyle,
|
|
debug: debug,
|
|
}
|
|
client, err := s.getClientWithCtx(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s.client = client
|
|
return s, s.validate(ctx)
|
|
}
|
|
|
|
func (s *S3Proxy) validate(ctx context.Context) error {
|
|
if s.metaBucket != "" && !s.bucketExists(ctx, s.metaBucket) {
|
|
return fmt.Errorf("the provided meta bucket doesn't exist")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *S3Proxy) ListBuckets(ctx context.Context, input s3response.ListBucketsInput) (s3response.ListAllMyBucketsResult, error) {
|
|
output, err := s.client.ListBuckets(ctx, &s3.ListBucketsInput{
|
|
ContinuationToken: &input.ContinuationToken,
|
|
MaxBuckets: &input.MaxBuckets,
|
|
Prefix: &input.Prefix,
|
|
})
|
|
if err != nil {
|
|
return s3response.ListAllMyBucketsResult{}, handleError(err)
|
|
}
|
|
|
|
var buckets []s3response.ListAllMyBucketsEntry
|
|
for _, b := range output.Buckets {
|
|
if *b.Name == s.metaBucket {
|
|
continue
|
|
}
|
|
if input.IsAdmin || s.metaBucket == "" {
|
|
buckets = append(buckets, s3response.ListAllMyBucketsEntry{
|
|
Name: *b.Name,
|
|
CreationDate: *b.CreationDate,
|
|
})
|
|
continue
|
|
}
|
|
|
|
data, err := s.getMetaBucketObjData(ctx, *b.Name, metaPrefixAcl, false)
|
|
if err != nil {
|
|
return s3response.ListAllMyBucketsResult{}, handleError(err)
|
|
}
|
|
|
|
acl, err := auth.ParseACL(data)
|
|
if err != nil {
|
|
return s3response.ListAllMyBucketsResult{}, err
|
|
}
|
|
|
|
if acl.Owner == input.Owner {
|
|
buckets = append(buckets, s3response.ListAllMyBucketsEntry{
|
|
Name: *b.Name,
|
|
CreationDate: *b.CreationDate,
|
|
})
|
|
}
|
|
}
|
|
|
|
return s3response.ListAllMyBucketsResult{
|
|
Owner: s3response.CanonicalUser{
|
|
ID: *output.Owner.ID,
|
|
},
|
|
Buckets: s3response.ListAllMyBucketsList{
|
|
Bucket: buckets,
|
|
},
|
|
ContinuationToken: backend.GetStringFromPtr(output.ContinuationToken),
|
|
Prefix: backend.GetStringFromPtr(output.Prefix),
|
|
}, nil
|
|
}
|
|
|
|
func (s *S3Proxy) HeadBucket(ctx context.Context, input *s3.HeadBucketInput) (*s3.HeadBucketOutput, error) {
|
|
if input.ExpectedBucketOwner != nil && *input.ExpectedBucketOwner == "" {
|
|
input.ExpectedBucketOwner = nil
|
|
}
|
|
out, err := s.client.HeadBucket(ctx, input)
|
|
return out, handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) CreateBucket(ctx context.Context, input *s3.CreateBucketInput, acl []byte) error {
|
|
if input.GrantFullControl != nil && *input.GrantFullControl == "" {
|
|
input.GrantFullControl = nil
|
|
}
|
|
if input.GrantRead != nil && *input.GrantRead == "" {
|
|
input.GrantRead = nil
|
|
}
|
|
if input.GrantReadACP != nil && *input.GrantReadACP == "" {
|
|
input.GrantReadACP = nil
|
|
}
|
|
if input.GrantWrite != nil && *input.GrantWrite == "" {
|
|
input.GrantWrite = nil
|
|
}
|
|
if input.GrantWriteACP != nil && *input.GrantWriteACP == "" {
|
|
input.GrantWriteACP = nil
|
|
}
|
|
if *input.Bucket == s.metaBucket {
|
|
return s3err.GetAPIError(s3err.ErrBucketAlreadyExists)
|
|
}
|
|
|
|
acct, ok := ctx.Value("account").(auth.Account)
|
|
if !ok {
|
|
acct = auth.Account{}
|
|
}
|
|
|
|
if s.metaBucket != "" {
|
|
data, err := s.getMetaBucketObjData(ctx, *input.Bucket, metaPrefixAcl, true)
|
|
if err == nil {
|
|
acl, err := auth.ParseACL(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if acl.Owner == acct.Access {
|
|
return s3err.GetAPIError(s3err.ErrBucketAlreadyOwnedByYou)
|
|
}
|
|
return s3err.GetAPIError(s3err.ErrBucketAlreadyExists)
|
|
}
|
|
}
|
|
|
|
_, err := s.client.CreateBucket(ctx, input)
|
|
if err != nil {
|
|
return handleError(err)
|
|
}
|
|
|
|
// Store bucket default acl
|
|
if s.metaBucket != "" {
|
|
err = s.putMetaBucketObj(ctx, *input.Bucket, acl, metaPrefixAcl)
|
|
if err != nil {
|
|
// attempt to cleanup
|
|
_ = s.DeleteBucket(ctx, *input.Bucket)
|
|
return handleError(err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *S3Proxy) DeleteBucket(ctx context.Context, bucket string) error {
|
|
if bucket == s.metaBucket {
|
|
return s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
_, err := s.client.DeleteBucket(ctx, &s3.DeleteBucketInput{
|
|
Bucket: &bucket,
|
|
})
|
|
return handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) PutBucketOwnershipControls(ctx context.Context, bucket string, ownership types.ObjectOwnership) error {
|
|
if bucket == s.metaBucket {
|
|
return s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
_, err := s.client.PutBucketOwnershipControls(ctx, &s3.PutBucketOwnershipControlsInput{
|
|
Bucket: &bucket,
|
|
OwnershipControls: &types.OwnershipControls{
|
|
Rules: []types.OwnershipControlsRule{
|
|
{
|
|
ObjectOwnership: ownership,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
return handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) GetBucketOwnershipControls(ctx context.Context, bucket string) (types.ObjectOwnership, error) {
|
|
if bucket == s.metaBucket {
|
|
return "", s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
var ownship types.ObjectOwnership
|
|
resp, err := s.client.GetBucketOwnershipControls(ctx, &s3.GetBucketOwnershipControlsInput{
|
|
Bucket: &bucket,
|
|
})
|
|
if err != nil {
|
|
return ownship, handleError(err)
|
|
}
|
|
return resp.OwnershipControls.Rules[0].ObjectOwnership, nil
|
|
}
|
|
func (s *S3Proxy) DeleteBucketOwnershipControls(ctx context.Context, bucket string) error {
|
|
if bucket == s.metaBucket {
|
|
return s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
_, err := s.client.DeleteBucketOwnershipControls(ctx, &s3.DeleteBucketOwnershipControlsInput{
|
|
Bucket: &bucket,
|
|
})
|
|
return handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) PutBucketVersioning(ctx context.Context, bucket string, status types.BucketVersioningStatus) error {
|
|
if bucket == s.metaBucket {
|
|
return s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
_, err := s.client.PutBucketVersioning(ctx, &s3.PutBucketVersioningInput{
|
|
Bucket: &bucket,
|
|
VersioningConfiguration: &types.VersioningConfiguration{
|
|
Status: status,
|
|
},
|
|
})
|
|
|
|
return handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) GetBucketVersioning(ctx context.Context, bucket string) (s3response.GetBucketVersioningOutput, error) {
|
|
if bucket == s.metaBucket {
|
|
return s3response.GetBucketVersioningOutput{}, s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
out, err := s.client.GetBucketVersioning(ctx, &s3.GetBucketVersioningInput{
|
|
Bucket: &bucket,
|
|
})
|
|
if err != nil {
|
|
return s3response.GetBucketVersioningOutput{}, handleError(err)
|
|
}
|
|
|
|
return s3response.GetBucketVersioningOutput{
|
|
Status: &out.Status,
|
|
MFADelete: &out.MFADelete,
|
|
}, nil
|
|
}
|
|
|
|
func (s *S3Proxy) ListObjectVersions(ctx context.Context, input *s3.ListObjectVersionsInput) (s3response.ListVersionsResult, error) {
|
|
if *input.Bucket == s.metaBucket {
|
|
return s3response.ListVersionsResult{}, s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
if input.Delimiter != nil && *input.Delimiter == "" {
|
|
input.Delimiter = nil
|
|
}
|
|
if input.Prefix != nil && *input.Prefix == "" {
|
|
input.Prefix = nil
|
|
}
|
|
if input.KeyMarker != nil && *input.KeyMarker == "" {
|
|
input.KeyMarker = nil
|
|
}
|
|
if input.VersionIdMarker != nil && *input.VersionIdMarker == "" {
|
|
input.VersionIdMarker = nil
|
|
}
|
|
if input.MaxKeys != nil && *input.MaxKeys == 0 {
|
|
input.MaxKeys = nil
|
|
}
|
|
if input.ExpectedBucketOwner != nil && *input.ExpectedBucketOwner == "" {
|
|
input.ExpectedBucketOwner = nil
|
|
}
|
|
|
|
out, err := s.client.ListObjectVersions(ctx, input)
|
|
if err != nil {
|
|
return s3response.ListVersionsResult{}, handleError(err)
|
|
}
|
|
|
|
return s3response.ListVersionsResult{
|
|
CommonPrefixes: out.CommonPrefixes,
|
|
DeleteMarkers: out.DeleteMarkers,
|
|
Delimiter: out.Delimiter,
|
|
EncodingType: out.EncodingType,
|
|
IsTruncated: out.IsTruncated,
|
|
KeyMarker: out.KeyMarker,
|
|
MaxKeys: out.MaxKeys,
|
|
Name: out.Name,
|
|
NextKeyMarker: out.NextKeyMarker,
|
|
NextVersionIdMarker: out.NextVersionIdMarker,
|
|
Prefix: out.Prefix,
|
|
VersionIdMarker: input.VersionIdMarker,
|
|
Versions: convertObjectVersions(out.Versions),
|
|
}, nil
|
|
}
|
|
|
|
var defTime = time.Time{}
|
|
|
|
func (s *S3Proxy) CreateMultipartUpload(ctx context.Context, input s3response.CreateMultipartUploadInput) (s3response.InitiateMultipartUploadResult, error) {
|
|
if *input.Bucket == s.metaBucket {
|
|
return s3response.InitiateMultipartUploadResult{}, s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
if input.CacheControl != nil && *input.CacheControl == "" {
|
|
input.CacheControl = nil
|
|
}
|
|
if input.ContentDisposition != nil && *input.ContentDisposition == "" {
|
|
input.ContentDisposition = nil
|
|
}
|
|
if input.ContentEncoding != nil && *input.ContentEncoding == "" {
|
|
input.ContentEncoding = nil
|
|
}
|
|
if input.ContentLanguage != nil && *input.ContentLanguage == "" {
|
|
input.ContentLanguage = nil
|
|
}
|
|
if input.ContentType != nil && *input.ContentType == "" {
|
|
input.ContentType = nil
|
|
}
|
|
if input.Expires != nil && *input.Expires == "" {
|
|
input.Expires = nil
|
|
}
|
|
if input.GrantFullControl != nil && *input.GrantFullControl == "" {
|
|
input.GrantFullControl = nil
|
|
}
|
|
if input.GrantRead != nil && *input.GrantRead == "" {
|
|
input.GrantRead = nil
|
|
}
|
|
if input.GrantReadACP != nil && *input.GrantReadACP == "" {
|
|
input.GrantReadACP = nil
|
|
}
|
|
if input.GrantWriteACP != nil && *input.GrantWriteACP == "" {
|
|
input.GrantWriteACP = nil
|
|
}
|
|
if input.ExpectedBucketOwner != nil && *input.ExpectedBucketOwner == "" {
|
|
input.ExpectedBucketOwner = nil
|
|
}
|
|
if input.ObjectLockRetainUntilDate != nil && (*input.ObjectLockRetainUntilDate).Equal(defTime) {
|
|
input.ObjectLockRetainUntilDate = nil
|
|
}
|
|
if input.SSECustomerAlgorithm != nil && *input.SSECustomerAlgorithm == "" {
|
|
input.SSECustomerAlgorithm = nil
|
|
}
|
|
if input.SSECustomerKey != nil && *input.SSECustomerKey == "" {
|
|
input.SSECustomerKey = nil
|
|
}
|
|
if input.SSECustomerKeyMD5 != nil && *input.SSECustomerKeyMD5 == "" {
|
|
input.SSECustomerKeyMD5 = nil
|
|
}
|
|
if input.SSEKMSKeyId != nil && *input.SSEKMSKeyId == "" {
|
|
input.SSEKMSKeyId = nil
|
|
}
|
|
if input.SSEKMSEncryptionContext != nil && *input.SSEKMSEncryptionContext == "" {
|
|
input.SSEKMSEncryptionContext = nil
|
|
}
|
|
if input.Tagging != nil && *input.Tagging == "" {
|
|
input.Tagging = nil
|
|
}
|
|
if input.WebsiteRedirectLocation != nil && *input.WebsiteRedirectLocation == "" {
|
|
input.WebsiteRedirectLocation = nil
|
|
}
|
|
|
|
var expires *time.Time
|
|
if input.Expires != nil {
|
|
exp, err := time.Parse(time.RFC1123, *input.Expires)
|
|
if err == nil {
|
|
expires = &exp
|
|
}
|
|
}
|
|
|
|
out, err := s.client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{
|
|
Bucket: input.Bucket,
|
|
Key: input.Key,
|
|
ExpectedBucketOwner: input.ExpectedBucketOwner,
|
|
CacheControl: input.CacheControl,
|
|
ContentDisposition: input.ContentDisposition,
|
|
ContentEncoding: input.ContentEncoding,
|
|
ContentLanguage: input.ContentLanguage,
|
|
ContentType: input.ContentType,
|
|
Expires: expires,
|
|
SSECustomerAlgorithm: input.SSECustomerAlgorithm,
|
|
SSECustomerKey: input.SSECustomerKey,
|
|
SSECustomerKeyMD5: input.SSECustomerKeyMD5,
|
|
SSEKMSEncryptionContext: input.SSEKMSEncryptionContext,
|
|
SSEKMSKeyId: input.SSEKMSKeyId,
|
|
GrantFullControl: input.GrantFullControl,
|
|
GrantRead: input.GrantRead,
|
|
GrantReadACP: input.GrantReadACP,
|
|
GrantWriteACP: input.GrantWriteACP,
|
|
Tagging: input.Tagging,
|
|
WebsiteRedirectLocation: input.WebsiteRedirectLocation,
|
|
BucketKeyEnabled: input.BucketKeyEnabled,
|
|
ObjectLockRetainUntilDate: input.ObjectLockRetainUntilDate,
|
|
Metadata: input.Metadata,
|
|
ACL: input.ACL,
|
|
ChecksumAlgorithm: input.ChecksumAlgorithm,
|
|
ChecksumType: input.ChecksumType,
|
|
ObjectLockLegalHoldStatus: input.ObjectLockLegalHoldStatus,
|
|
ObjectLockMode: input.ObjectLockMode,
|
|
RequestPayer: input.RequestPayer,
|
|
ServerSideEncryption: input.ServerSideEncryption,
|
|
StorageClass: input.StorageClass,
|
|
})
|
|
if err != nil {
|
|
return s3response.InitiateMultipartUploadResult{}, handleError(err)
|
|
}
|
|
|
|
return s3response.InitiateMultipartUploadResult{
|
|
Bucket: *out.Bucket,
|
|
Key: *out.Key,
|
|
UploadId: *out.UploadId,
|
|
}, nil
|
|
}
|
|
|
|
func (s *S3Proxy) CompleteMultipartUpload(ctx context.Context, input *s3.CompleteMultipartUploadInput) (s3response.CompleteMultipartUploadResult, string, error) {
|
|
var res s3response.CompleteMultipartUploadResult
|
|
|
|
if *input.Bucket == s.metaBucket {
|
|
return res, "", s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
if input.ChecksumCRC32 != nil && *input.ChecksumCRC32 == "" {
|
|
input.ChecksumCRC32 = nil
|
|
}
|
|
if input.ChecksumCRC32C != nil && *input.ChecksumCRC32C == "" {
|
|
input.ChecksumCRC32C = nil
|
|
}
|
|
if input.ChecksumCRC64NVME != nil && *input.ChecksumCRC64NVME == "" {
|
|
input.ChecksumCRC64NVME = nil
|
|
}
|
|
if input.ChecksumSHA1 != nil && *input.ChecksumSHA1 == "" {
|
|
input.ChecksumSHA1 = nil
|
|
}
|
|
if input.ChecksumSHA256 != nil && *input.ChecksumSHA256 == "" {
|
|
input.ChecksumSHA256 = nil
|
|
}
|
|
if input.ExpectedBucketOwner != nil && *input.ExpectedBucketOwner == "" {
|
|
input.ExpectedBucketOwner = nil
|
|
}
|
|
if input.IfMatch != nil && *input.IfMatch == "" {
|
|
input.IfMatch = nil
|
|
}
|
|
if input.IfNoneMatch != nil && *input.IfNoneMatch == "" {
|
|
input.IfNoneMatch = nil
|
|
}
|
|
if input.MpuObjectSize != nil && *input.MpuObjectSize == 0 {
|
|
input.MpuObjectSize = nil
|
|
}
|
|
if input.SSECustomerAlgorithm != nil && *input.SSECustomerAlgorithm == "" {
|
|
input.SSECustomerAlgorithm = nil
|
|
}
|
|
if input.SSECustomerKey != nil && *input.SSECustomerKey == "" {
|
|
input.SSECustomerKey = nil
|
|
}
|
|
if input.SSECustomerKeyMD5 != nil && *input.SSECustomerKeyMD5 == "" {
|
|
input.SSECustomerKeyMD5 = nil
|
|
}
|
|
|
|
var versionid string
|
|
out, err := s.client.CompleteMultipartUpload(ctx, input)
|
|
if out != nil {
|
|
res = s3response.CompleteMultipartUploadResult{
|
|
Location: out.Location,
|
|
Bucket: out.Bucket,
|
|
Key: out.Key,
|
|
ETag: out.ETag,
|
|
ChecksumCRC32: out.ChecksumCRC32,
|
|
ChecksumCRC32C: out.ChecksumCRC32C,
|
|
ChecksumCRC64NVME: out.ChecksumCRC64NVME,
|
|
ChecksumSHA1: out.ChecksumSHA1,
|
|
ChecksumSHA256: out.ChecksumSHA256,
|
|
ChecksumType: &out.ChecksumType,
|
|
}
|
|
if out.VersionId != nil {
|
|
versionid = *out.VersionId
|
|
}
|
|
}
|
|
|
|
return res, versionid, handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) AbortMultipartUpload(ctx context.Context, input *s3.AbortMultipartUploadInput) error {
|
|
if *input.Bucket == s.metaBucket {
|
|
return s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
if input.ExpectedBucketOwner != nil && *input.ExpectedBucketOwner == "" {
|
|
input.ExpectedBucketOwner = nil
|
|
}
|
|
if input.IfMatchInitiatedTime != nil && (*input.IfMatchInitiatedTime).Equal(defTime) {
|
|
input.IfMatchInitiatedTime = nil
|
|
}
|
|
_, err := s.client.AbortMultipartUpload(ctx, input)
|
|
return handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) ListMultipartUploads(ctx context.Context, input *s3.ListMultipartUploadsInput) (s3response.ListMultipartUploadsResult, error) {
|
|
if *input.Bucket == s.metaBucket {
|
|
return s3response.ListMultipartUploadsResult{}, s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
if input.Delimiter != nil && *input.Delimiter == "" {
|
|
input.Delimiter = nil
|
|
}
|
|
if input.ExpectedBucketOwner != nil && *input.ExpectedBucketOwner == "" {
|
|
input.ExpectedBucketOwner = nil
|
|
}
|
|
if input.KeyMarker != nil && *input.KeyMarker == "" {
|
|
input.KeyMarker = nil
|
|
}
|
|
if input.MaxUploads != nil && *input.MaxUploads == 0 {
|
|
input.MaxUploads = nil
|
|
}
|
|
if input.Prefix != nil && *input.Prefix == "" {
|
|
input.Prefix = nil
|
|
}
|
|
if input.UploadIdMarker != nil && *input.UploadIdMarker == "" {
|
|
input.UploadIdMarker = nil
|
|
}
|
|
|
|
output, err := s.client.ListMultipartUploads(ctx, input)
|
|
if err != nil {
|
|
return s3response.ListMultipartUploadsResult{}, handleError(err)
|
|
}
|
|
|
|
var uploads []s3response.Upload
|
|
for _, u := range output.Uploads {
|
|
uploads = append(uploads, s3response.Upload{
|
|
Key: *u.Key,
|
|
UploadID: *u.UploadId,
|
|
Initiator: s3response.Initiator{
|
|
ID: *u.Initiator.ID,
|
|
DisplayName: *u.Initiator.DisplayName,
|
|
},
|
|
Owner: s3response.Owner{
|
|
ID: *u.Owner.ID,
|
|
DisplayName: *u.Owner.DisplayName,
|
|
},
|
|
StorageClass: u.StorageClass,
|
|
Initiated: *u.Initiated,
|
|
ChecksumAlgorithm: u.ChecksumAlgorithm,
|
|
ChecksumType: u.ChecksumType,
|
|
})
|
|
}
|
|
|
|
var cps []s3response.CommonPrefix
|
|
for _, c := range output.CommonPrefixes {
|
|
cps = append(cps, s3response.CommonPrefix{
|
|
Prefix: *c.Prefix,
|
|
})
|
|
}
|
|
|
|
return s3response.ListMultipartUploadsResult{
|
|
Bucket: *output.Bucket,
|
|
KeyMarker: *output.KeyMarker,
|
|
UploadIDMarker: *output.UploadIdMarker,
|
|
NextKeyMarker: *output.NextKeyMarker,
|
|
NextUploadIDMarker: *output.NextUploadIdMarker,
|
|
Delimiter: *output.Delimiter,
|
|
Prefix: *output.Prefix,
|
|
EncodingType: string(output.EncodingType),
|
|
MaxUploads: int(*output.MaxUploads),
|
|
IsTruncated: *output.IsTruncated,
|
|
Uploads: uploads,
|
|
CommonPrefixes: cps,
|
|
}, nil
|
|
}
|
|
|
|
func (s *S3Proxy) ListParts(ctx context.Context, input *s3.ListPartsInput) (s3response.ListPartsResult, error) {
|
|
if *input.Bucket == s.metaBucket {
|
|
return s3response.ListPartsResult{}, s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
if input.ExpectedBucketOwner != nil && *input.ExpectedBucketOwner == "" {
|
|
input.ExpectedBucketOwner = nil
|
|
}
|
|
if input.MaxParts != nil && *input.MaxParts == 0 {
|
|
input.MaxParts = nil
|
|
}
|
|
if input.PartNumberMarker != nil && *input.PartNumberMarker == "" {
|
|
input.PartNumberMarker = nil
|
|
}
|
|
if input.SSECustomerAlgorithm != nil && *input.SSECustomerAlgorithm == "" {
|
|
input.SSECustomerAlgorithm = nil
|
|
}
|
|
if input.SSECustomerKey != nil && *input.SSECustomerKey == "" {
|
|
input.SSECustomerKey = nil
|
|
}
|
|
if input.SSECustomerKeyMD5 != nil && *input.SSECustomerKeyMD5 == "" {
|
|
input.SSECustomerKeyMD5 = nil
|
|
}
|
|
|
|
output, err := s.client.ListParts(ctx, input)
|
|
if err != nil {
|
|
return s3response.ListPartsResult{}, handleError(err)
|
|
}
|
|
|
|
var parts []s3response.Part
|
|
for _, p := range output.Parts {
|
|
parts = append(parts, s3response.Part{
|
|
PartNumber: int(*p.PartNumber),
|
|
LastModified: *p.LastModified,
|
|
ETag: *p.ETag,
|
|
Size: *p.Size,
|
|
ChecksumCRC32: p.ChecksumCRC32,
|
|
ChecksumCRC32C: p.ChecksumCRC32C,
|
|
ChecksumCRC64NVME: p.ChecksumCRC64NVME,
|
|
ChecksumSHA1: p.ChecksumSHA1,
|
|
ChecksumSHA256: p.ChecksumSHA256,
|
|
})
|
|
}
|
|
pnm, err := strconv.Atoi(*output.PartNumberMarker)
|
|
if err != nil {
|
|
return s3response.ListPartsResult{},
|
|
fmt.Errorf("parse part number marker: %w", err)
|
|
}
|
|
|
|
npmn, err := strconv.Atoi(*output.NextPartNumberMarker)
|
|
if err != nil {
|
|
return s3response.ListPartsResult{},
|
|
fmt.Errorf("parse next part number marker: %w", err)
|
|
}
|
|
|
|
return s3response.ListPartsResult{
|
|
Bucket: *output.Bucket,
|
|
Key: *output.Key,
|
|
UploadID: *output.UploadId,
|
|
Initiator: s3response.Initiator{
|
|
ID: *output.Initiator.ID,
|
|
DisplayName: *output.Initiator.DisplayName,
|
|
},
|
|
Owner: s3response.Owner{
|
|
ID: *output.Owner.ID,
|
|
DisplayName: *output.Owner.DisplayName,
|
|
},
|
|
StorageClass: output.StorageClass,
|
|
PartNumberMarker: pnm,
|
|
NextPartNumberMarker: npmn,
|
|
MaxParts: int(*output.MaxParts),
|
|
IsTruncated: *output.IsTruncated,
|
|
Parts: parts,
|
|
ChecksumAlgorithm: output.ChecksumAlgorithm,
|
|
ChecksumType: output.ChecksumType,
|
|
}, nil
|
|
}
|
|
|
|
func (s *S3Proxy) UploadPart(ctx context.Context, input *s3.UploadPartInput) (*s3.UploadPartOutput, error) {
|
|
if *input.Bucket == s.metaBucket {
|
|
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
if input.ChecksumCRC32 != nil && *input.ChecksumCRC32 == "" {
|
|
input.ChecksumCRC32 = nil
|
|
}
|
|
if input.ChecksumCRC32C != nil && *input.ChecksumCRC32C == "" {
|
|
input.ChecksumCRC32C = nil
|
|
}
|
|
if input.ChecksumCRC64NVME != nil && *input.ChecksumCRC64NVME == "" {
|
|
input.ChecksumCRC64NVME = nil
|
|
}
|
|
if input.ChecksumSHA1 != nil && *input.ChecksumSHA1 == "" {
|
|
input.ChecksumSHA1 = nil
|
|
}
|
|
if input.ChecksumSHA256 != nil && *input.ChecksumSHA256 == "" {
|
|
input.ChecksumSHA256 = nil
|
|
}
|
|
if input.ContentMD5 != nil && *input.ContentMD5 == "" {
|
|
input.ContentMD5 = nil
|
|
}
|
|
if input.ExpectedBucketOwner != nil && *input.ExpectedBucketOwner == "" {
|
|
input.ExpectedBucketOwner = nil
|
|
}
|
|
if input.SSECustomerAlgorithm != nil && *input.SSECustomerAlgorithm == "" {
|
|
input.SSECustomerAlgorithm = nil
|
|
}
|
|
if input.SSECustomerKey != nil && *input.SSECustomerKey == "" {
|
|
input.SSECustomerKey = nil
|
|
}
|
|
if input.SSECustomerKeyMD5 != nil && *input.SSECustomerKeyMD5 == "" {
|
|
input.SSECustomerKeyMD5 = nil
|
|
}
|
|
|
|
// streaming backend is not seekable,
|
|
// use unsigned payload for streaming ops
|
|
output, err := s.client.UploadPart(ctx, input, s3.WithAPIOptions(
|
|
v4.SwapComputePayloadSHA256ForUnsignedPayloadMiddleware,
|
|
))
|
|
return output, handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) UploadPartCopy(ctx context.Context, input *s3.UploadPartCopyInput) (s3response.CopyPartResult, error) {
|
|
if *input.Bucket == s.metaBucket {
|
|
return s3response.CopyPartResult{}, s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
if input.CopySourceIfMatch != nil && *input.CopySourceIfMatch == "" {
|
|
input.CopySourceIfMatch = nil
|
|
}
|
|
if input.CopySourceIfModifiedSince != nil && (*input.CopySourceIfModifiedSince).Equal(defTime) {
|
|
input.CopySourceIfModifiedSince = nil
|
|
}
|
|
if input.CopySourceIfNoneMatch != nil && *input.CopySourceIfNoneMatch == "" {
|
|
input.CopySourceIfNoneMatch = nil
|
|
}
|
|
if input.CopySourceIfUnmodifiedSince != nil && (*input.CopySourceIfUnmodifiedSince).Equal(defTime) {
|
|
input.CopySourceIfUnmodifiedSince = nil
|
|
}
|
|
if input.CopySourceRange != nil && *input.CopySourceRange == "" {
|
|
input.CopySourceRange = nil
|
|
}
|
|
if input.CopySourceSSECustomerAlgorithm != nil && *input.CopySourceSSECustomerAlgorithm == "" {
|
|
input.CopySourceSSECustomerAlgorithm = nil
|
|
}
|
|
if input.CopySourceSSECustomerKey != nil && *input.CopySourceSSECustomerKey == "" {
|
|
input.CopySourceSSECustomerKey = nil
|
|
}
|
|
if input.CopySourceSSECustomerKeyMD5 != nil && *input.CopySourceSSECustomerKeyMD5 == "" {
|
|
input.CopySourceSSECustomerKeyMD5 = nil
|
|
}
|
|
if input.ExpectedBucketOwner != nil && *input.ExpectedBucketOwner == "" {
|
|
input.ExpectedBucketOwner = nil
|
|
}
|
|
if input.ExpectedSourceBucketOwner != nil && *input.ExpectedSourceBucketOwner == "" {
|
|
input.ExpectedSourceBucketOwner = nil
|
|
}
|
|
if input.SSECustomerAlgorithm != nil && *input.SSECustomerAlgorithm == "" {
|
|
input.SSECustomerAlgorithm = nil
|
|
}
|
|
if input.SSECustomerKey != nil && *input.SSECustomerKey == "" {
|
|
input.SSECustomerKey = nil
|
|
}
|
|
if input.SSECustomerKeyMD5 != nil && *input.SSECustomerKeyMD5 == "" {
|
|
input.SSECustomerKeyMD5 = nil
|
|
}
|
|
|
|
output, err := s.client.UploadPartCopy(ctx, input)
|
|
if err != nil {
|
|
return s3response.CopyPartResult{}, handleError(err)
|
|
}
|
|
|
|
return s3response.CopyPartResult{
|
|
LastModified: *output.CopyPartResult.LastModified,
|
|
ETag: output.CopyPartResult.ETag,
|
|
ChecksumCRC32: output.CopyPartResult.ChecksumCRC32,
|
|
ChecksumCRC32C: output.CopyPartResult.ChecksumCRC32C,
|
|
ChecksumCRC64NVME: output.CopyPartResult.ChecksumCRC64NVME,
|
|
ChecksumSHA1: output.CopyPartResult.ChecksumSHA1,
|
|
ChecksumSHA256: output.CopyPartResult.ChecksumSHA256,
|
|
}, nil
|
|
}
|
|
|
|
func (s *S3Proxy) PutObject(ctx context.Context, input s3response.PutObjectInput) (s3response.PutObjectOutput, error) {
|
|
if *input.Bucket == s.metaBucket {
|
|
return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
if input.CacheControl != nil && *input.CacheControl == "" {
|
|
input.CacheControl = nil
|
|
}
|
|
if input.ChecksumCRC32 != nil && *input.ChecksumCRC32 == "" {
|
|
input.ChecksumCRC32 = nil
|
|
}
|
|
if input.ChecksumCRC32C != nil && *input.ChecksumCRC32C == "" {
|
|
input.ChecksumCRC32C = nil
|
|
}
|
|
if input.ChecksumCRC64NVME != nil && *input.ChecksumCRC64NVME == "" {
|
|
input.ChecksumCRC64NVME = nil
|
|
}
|
|
if input.ChecksumSHA1 != nil && *input.ChecksumSHA1 == "" {
|
|
input.ChecksumSHA1 = nil
|
|
}
|
|
if input.ChecksumSHA256 != nil && *input.ChecksumSHA256 == "" {
|
|
input.ChecksumSHA256 = nil
|
|
}
|
|
if input.ContentDisposition != nil && *input.ContentDisposition == "" {
|
|
input.ContentDisposition = nil
|
|
}
|
|
if input.ContentEncoding != nil && *input.ContentEncoding == "" {
|
|
input.ContentEncoding = nil
|
|
}
|
|
if input.ContentLanguage != nil && *input.ContentLanguage == "" {
|
|
input.ContentLanguage = nil
|
|
}
|
|
if input.ContentMD5 != nil && *input.ContentMD5 == "" {
|
|
input.ContentMD5 = nil
|
|
}
|
|
if input.ContentType != nil && *input.ContentType == "" {
|
|
input.ContentType = nil
|
|
}
|
|
if input.ExpectedBucketOwner != nil && *input.ExpectedBucketOwner == "" {
|
|
input.ExpectedBucketOwner = nil
|
|
}
|
|
if input.Expires != nil && *input.Expires == "" {
|
|
input.Expires = nil
|
|
}
|
|
if input.GrantFullControl != nil && *input.GrantFullControl == "" {
|
|
input.GrantFullControl = nil
|
|
}
|
|
if input.GrantRead != nil && *input.GrantRead == "" {
|
|
input.GrantRead = nil
|
|
}
|
|
if input.GrantReadACP != nil && *input.GrantReadACP == "" {
|
|
input.GrantReadACP = nil
|
|
}
|
|
if input.GrantWriteACP != nil && *input.GrantWriteACP == "" {
|
|
input.GrantWriteACP = nil
|
|
}
|
|
if input.IfMatch != nil && *input.IfMatch == "" {
|
|
input.IfMatch = nil
|
|
}
|
|
if input.IfNoneMatch != nil && *input.IfNoneMatch == "" {
|
|
input.IfNoneMatch = nil
|
|
}
|
|
if input.SSECustomerAlgorithm != nil && *input.SSECustomerAlgorithm == "" {
|
|
input.SSECustomerAlgorithm = nil
|
|
}
|
|
if input.SSECustomerKey != nil && *input.SSECustomerKey == "" {
|
|
input.SSECustomerKey = nil
|
|
}
|
|
if input.SSECustomerKeyMD5 != nil && *input.SSECustomerKeyMD5 == "" {
|
|
input.SSECustomerKeyMD5 = nil
|
|
}
|
|
if input.SSEKMSEncryptionContext != nil && *input.SSEKMSEncryptionContext == "" {
|
|
input.SSEKMSEncryptionContext = nil
|
|
}
|
|
if input.SSEKMSKeyId != nil && *input.SSEKMSKeyId == "" {
|
|
input.SSEKMSKeyId = nil
|
|
}
|
|
if input.Tagging != nil && *input.Tagging == "" {
|
|
input.Tagging = nil
|
|
}
|
|
if input.WebsiteRedirectLocation != nil && *input.WebsiteRedirectLocation == "" {
|
|
input.WebsiteRedirectLocation = nil
|
|
}
|
|
|
|
// no object lock for backend
|
|
input.ObjectLockRetainUntilDate = nil
|
|
input.ObjectLockMode = ""
|
|
input.ObjectLockLegalHoldStatus = ""
|
|
|
|
var expire *time.Time
|
|
if input.Expires != nil {
|
|
exp, err := time.Parse(time.RFC1123, *input.Expires)
|
|
if err == nil {
|
|
expire = &exp
|
|
}
|
|
}
|
|
|
|
// streaming backend is not seekable,
|
|
// use unsigned payload for streaming ops
|
|
output, err := s.client.PutObject(ctx, &s3.PutObjectInput{
|
|
Bucket: input.Bucket,
|
|
Key: input.Key,
|
|
ContentLength: input.ContentLength,
|
|
ContentType: input.ContentType,
|
|
ContentEncoding: input.ContentEncoding,
|
|
ContentDisposition: input.ContentDisposition,
|
|
ContentLanguage: input.ContentLanguage,
|
|
CacheControl: input.CacheControl,
|
|
Expires: expire,
|
|
Metadata: input.Metadata,
|
|
Body: input.Body,
|
|
Tagging: input.Tagging,
|
|
ObjectLockRetainUntilDate: input.ObjectLockRetainUntilDate,
|
|
ObjectLockMode: input.ObjectLockMode,
|
|
ObjectLockLegalHoldStatus: input.ObjectLockLegalHoldStatus,
|
|
ChecksumAlgorithm: input.ChecksumAlgorithm,
|
|
ChecksumCRC32: input.ChecksumCRC32,
|
|
ChecksumCRC32C: input.ChecksumCRC32C,
|
|
ChecksumSHA1: input.ChecksumSHA1,
|
|
ChecksumSHA256: input.ChecksumSHA256,
|
|
ChecksumCRC64NVME: input.ChecksumCRC64NVME,
|
|
ContentMD5: input.ContentMD5,
|
|
ExpectedBucketOwner: input.ExpectedBucketOwner,
|
|
GrantFullControl: input.GrantFullControl,
|
|
GrantRead: input.GrantRead,
|
|
GrantReadACP: input.GrantReadACP,
|
|
GrantWriteACP: input.GrantWriteACP,
|
|
IfMatch: input.IfMatch,
|
|
IfNoneMatch: input.IfNoneMatch,
|
|
SSECustomerAlgorithm: input.SSECustomerAlgorithm,
|
|
SSECustomerKey: input.SSECustomerKey,
|
|
SSECustomerKeyMD5: input.SSECustomerKeyMD5,
|
|
SSEKMSEncryptionContext: input.SSEKMSEncryptionContext,
|
|
SSEKMSKeyId: input.SSEKMSKeyId,
|
|
WebsiteRedirectLocation: input.WebsiteRedirectLocation,
|
|
}, s3.WithAPIOptions(
|
|
v4.SwapComputePayloadSHA256ForUnsignedPayloadMiddleware,
|
|
))
|
|
if err != nil {
|
|
return s3response.PutObjectOutput{}, handleError(err)
|
|
}
|
|
|
|
var versionID string
|
|
if output.VersionId != nil {
|
|
versionID = *output.VersionId
|
|
}
|
|
|
|
return s3response.PutObjectOutput{
|
|
ETag: *output.ETag,
|
|
VersionID: versionID,
|
|
ChecksumCRC32: output.ChecksumCRC32,
|
|
ChecksumCRC32C: output.ChecksumCRC32C,
|
|
ChecksumCRC64NVME: output.ChecksumCRC64NVME,
|
|
ChecksumSHA1: output.ChecksumSHA1,
|
|
ChecksumSHA256: output.ChecksumSHA256,
|
|
Size: output.Size,
|
|
}, nil
|
|
}
|
|
|
|
func (s *S3Proxy) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3.HeadObjectOutput, error) {
|
|
if *input.Bucket == s.metaBucket {
|
|
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
if input.ExpectedBucketOwner != nil && *input.ExpectedBucketOwner == "" {
|
|
input.ExpectedBucketOwner = nil
|
|
}
|
|
if input.IfMatch != nil && *input.IfMatch == "" {
|
|
input.IfMatch = nil
|
|
}
|
|
if input.IfModifiedSince != nil && (*input.IfModifiedSince).Equal(defTime) {
|
|
input.IfModifiedSince = nil
|
|
}
|
|
if input.IfNoneMatch != nil && *input.IfNoneMatch == "" {
|
|
input.IfNoneMatch = nil
|
|
}
|
|
if input.IfUnmodifiedSince != nil && (*input.IfUnmodifiedSince).Equal(defTime) {
|
|
input.IfUnmodifiedSince = nil
|
|
}
|
|
if input.PartNumber != nil && *input.PartNumber == 0 {
|
|
input.PartNumber = nil
|
|
}
|
|
if input.Range != nil && *input.Range == "" {
|
|
input.Range = nil
|
|
}
|
|
if input.ResponseCacheControl != nil && *input.ResponseCacheControl == "" {
|
|
input.ResponseCacheControl = nil
|
|
}
|
|
if input.ResponseContentDisposition != nil && *input.ResponseContentDisposition == "" {
|
|
input.ResponseContentDisposition = nil
|
|
}
|
|
if input.ResponseContentEncoding != nil && *input.ResponseContentEncoding == "" {
|
|
input.ResponseContentEncoding = nil
|
|
}
|
|
if input.ResponseContentLanguage != nil && *input.ResponseContentLanguage == "" {
|
|
input.ResponseContentLanguage = nil
|
|
}
|
|
if input.ResponseContentType != nil && *input.ResponseContentType == "" {
|
|
input.ResponseContentType = nil
|
|
}
|
|
if input.ResponseExpires != nil && (*input.ResponseExpires).Equal(defTime) {
|
|
input.ResponseExpires = nil
|
|
}
|
|
if input.SSECustomerAlgorithm != nil && *input.SSECustomerAlgorithm == "" {
|
|
input.SSECustomerAlgorithm = nil
|
|
}
|
|
if input.SSECustomerKey != nil && *input.SSECustomerKey == "" {
|
|
input.SSECustomerKey = nil
|
|
}
|
|
if input.SSECustomerKeyMD5 != nil && *input.SSECustomerKeyMD5 == "" {
|
|
input.SSECustomerKeyMD5 = nil
|
|
}
|
|
if input.VersionId != nil && *input.VersionId == "" {
|
|
input.VersionId = nil
|
|
}
|
|
|
|
out, err := s.client.HeadObject(ctx, input)
|
|
return out, handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) GetObject(ctx context.Context, input *s3.GetObjectInput) (*s3.GetObjectOutput, error) {
|
|
if *input.Bucket == s.metaBucket {
|
|
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
if input.ExpectedBucketOwner != nil && *input.ExpectedBucketOwner == "" {
|
|
input.ExpectedBucketOwner = nil
|
|
}
|
|
if input.IfMatch != nil && *input.IfMatch == "" {
|
|
input.IfMatch = nil
|
|
}
|
|
if input.IfModifiedSince != nil && (*input.IfModifiedSince).Equal(defTime) {
|
|
input.IfModifiedSince = nil
|
|
}
|
|
if input.IfNoneMatch != nil && *input.IfNoneMatch == "" {
|
|
input.IfNoneMatch = nil
|
|
}
|
|
if input.IfUnmodifiedSince != nil && (*input.IfUnmodifiedSince).Equal(defTime) {
|
|
input.IfUnmodifiedSince = nil
|
|
}
|
|
if input.PartNumber != nil && *input.PartNumber == 0 {
|
|
input.PartNumber = nil
|
|
}
|
|
if input.Range != nil && *input.Range == "" {
|
|
input.Range = nil
|
|
}
|
|
if input.ResponseCacheControl != nil && *input.ResponseCacheControl == "" {
|
|
input.ResponseCacheControl = nil
|
|
}
|
|
if input.ResponseContentDisposition != nil && *input.ResponseContentDisposition == "" {
|
|
input.ResponseContentDisposition = nil
|
|
}
|
|
if input.ResponseContentEncoding != nil && *input.ResponseContentEncoding == "" {
|
|
input.ResponseContentEncoding = nil
|
|
}
|
|
if input.ResponseContentLanguage != nil && *input.ResponseContentLanguage == "" {
|
|
input.ResponseContentLanguage = nil
|
|
}
|
|
if input.ResponseContentType != nil && *input.ResponseContentType == "" {
|
|
input.ResponseContentType = nil
|
|
}
|
|
if input.ResponseExpires != nil && (*input.ResponseExpires).Equal(defTime) {
|
|
input.ResponseExpires = nil
|
|
}
|
|
if input.SSECustomerAlgorithm != nil && *input.SSECustomerAlgorithm == "" {
|
|
input.SSECustomerAlgorithm = nil
|
|
}
|
|
if input.SSECustomerKey != nil && *input.SSECustomerKey == "" {
|
|
input.SSECustomerKey = nil
|
|
}
|
|
if input.SSECustomerKeyMD5 != nil && *input.SSECustomerKeyMD5 == "" {
|
|
input.SSECustomerKeyMD5 = nil
|
|
}
|
|
if input.VersionId != nil && *input.VersionId == "" {
|
|
input.VersionId = nil
|
|
}
|
|
|
|
output, err := s.client.GetObject(ctx, input)
|
|
if err != nil {
|
|
return nil, handleError(err)
|
|
}
|
|
|
|
return output, nil
|
|
}
|
|
|
|
func (s *S3Proxy) GetObjectAttributes(ctx context.Context, input *s3.GetObjectAttributesInput) (s3response.GetObjectAttributesResponse, error) {
|
|
if *input.Bucket == s.metaBucket {
|
|
return s3response.GetObjectAttributesResponse{}, s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
if input.ExpectedBucketOwner != nil && *input.ExpectedBucketOwner == "" {
|
|
input.ExpectedBucketOwner = nil
|
|
}
|
|
if input.MaxParts != nil && *input.MaxParts == 0 {
|
|
input.MaxParts = nil
|
|
}
|
|
if input.PartNumberMarker != nil && *input.PartNumberMarker == "" {
|
|
input.PartNumberMarker = nil
|
|
}
|
|
if input.SSECustomerAlgorithm != nil && *input.SSECustomerAlgorithm == "" {
|
|
input.SSECustomerAlgorithm = nil
|
|
}
|
|
if input.SSECustomerKey != nil && *input.SSECustomerKey == "" {
|
|
input.SSECustomerKey = nil
|
|
}
|
|
if input.SSECustomerKeyMD5 != nil && *input.SSECustomerKeyMD5 == "" {
|
|
input.SSECustomerKeyMD5 = nil
|
|
}
|
|
if input.VersionId != nil && *input.VersionId == "" {
|
|
input.VersionId = nil
|
|
}
|
|
|
|
out, err := s.client.GetObjectAttributes(ctx, input)
|
|
if err != nil {
|
|
return s3response.GetObjectAttributesResponse{}, handleError(err)
|
|
}
|
|
|
|
parts := s3response.ObjectParts{}
|
|
objParts := out.ObjectParts
|
|
if objParts != nil {
|
|
if objParts.PartNumberMarker != nil {
|
|
partNumberMarker, err := strconv.Atoi(*objParts.PartNumberMarker)
|
|
if err != nil {
|
|
parts.PartNumberMarker = partNumberMarker
|
|
}
|
|
if objParts.NextPartNumberMarker != nil {
|
|
nextPartNumberMarker, err := strconv.Atoi(*objParts.NextPartNumberMarker)
|
|
if err != nil {
|
|
parts.NextPartNumberMarker = nextPartNumberMarker
|
|
}
|
|
}
|
|
if objParts.IsTruncated != nil {
|
|
parts.IsTruncated = *objParts.IsTruncated
|
|
}
|
|
if objParts.MaxParts != nil {
|
|
parts.MaxParts = int(*objParts.MaxParts)
|
|
}
|
|
parts.Parts = objParts.Parts
|
|
}
|
|
}
|
|
|
|
return s3response.GetObjectAttributesResponse{
|
|
ETag: out.ETag,
|
|
LastModified: out.LastModified,
|
|
ObjectSize: out.ObjectSize,
|
|
StorageClass: out.StorageClass,
|
|
ObjectParts: &parts,
|
|
Checksum: out.Checksum,
|
|
}, nil
|
|
}
|
|
|
|
func (s *S3Proxy) CopyObject(ctx context.Context, input s3response.CopyObjectInput) (s3response.CopyObjectOutput, error) {
|
|
if *input.Bucket == s.metaBucket {
|
|
return s3response.CopyObjectOutput{}, s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
if input.CacheControl != nil && *input.CacheControl == "" {
|
|
input.CacheControl = nil
|
|
}
|
|
if input.ContentDisposition != nil && *input.ContentDisposition == "" {
|
|
input.ContentDisposition = nil
|
|
}
|
|
if input.ContentEncoding != nil && *input.ContentEncoding == "" {
|
|
input.ContentEncoding = nil
|
|
}
|
|
if input.ContentLanguage != nil && *input.ContentLanguage == "" {
|
|
input.ContentLanguage = nil
|
|
}
|
|
if input.ContentType != nil && *input.ContentType == "" {
|
|
input.ContentType = nil
|
|
}
|
|
if input.CopySourceIfMatch != nil && *input.CopySourceIfMatch == "" {
|
|
input.CopySourceIfMatch = nil
|
|
}
|
|
if input.CopySourceIfModifiedSince != nil && (*input.CopySourceIfModifiedSince).Equal(defTime) {
|
|
input.CopySourceIfModifiedSince = nil
|
|
}
|
|
if input.CopySourceIfNoneMatch != nil && *input.CopySourceIfNoneMatch == "" {
|
|
input.CopySourceIfNoneMatch = nil
|
|
}
|
|
if input.CopySourceIfUnmodifiedSince != nil && (*input.CopySourceIfUnmodifiedSince).Equal(defTime) {
|
|
input.CopySourceIfUnmodifiedSince = nil
|
|
}
|
|
if input.CopySourceSSECustomerAlgorithm != nil && *input.CopySourceSSECustomerAlgorithm == "" {
|
|
input.CopySourceSSECustomerAlgorithm = nil
|
|
}
|
|
if input.CopySourceSSECustomerKey != nil && *input.CopySourceSSECustomerKey == "" {
|
|
input.CopySourceSSECustomerKey = nil
|
|
}
|
|
if input.CopySourceSSECustomerKeyMD5 != nil && *input.CopySourceSSECustomerKeyMD5 == "" {
|
|
input.CopySourceSSECustomerKeyMD5 = nil
|
|
}
|
|
if input.ExpectedBucketOwner != nil && *input.ExpectedBucketOwner == "" {
|
|
input.ExpectedBucketOwner = nil
|
|
}
|
|
if input.ExpectedSourceBucketOwner != nil && *input.ExpectedSourceBucketOwner == "" {
|
|
input.ExpectedSourceBucketOwner = nil
|
|
}
|
|
if input.Expires != nil && *input.Expires == "" {
|
|
input.Expires = nil
|
|
}
|
|
if input.GrantFullControl != nil && *input.GrantFullControl == "" {
|
|
input.GrantFullControl = nil
|
|
}
|
|
if input.GrantRead != nil && *input.GrantRead == "" {
|
|
input.GrantRead = nil
|
|
}
|
|
if input.GrantReadACP != nil && *input.GrantReadACP == "" {
|
|
input.GrantReadACP = nil
|
|
}
|
|
if input.GrantWriteACP != nil && *input.GrantWriteACP == "" {
|
|
input.GrantWriteACP = nil
|
|
}
|
|
if input.ObjectLockRetainUntilDate != nil && (*input.ObjectLockRetainUntilDate).Equal(defTime) {
|
|
input.ObjectLockRetainUntilDate = nil
|
|
}
|
|
if input.SSECustomerAlgorithm != nil && *input.SSECustomerAlgorithm == "" {
|
|
input.SSECustomerAlgorithm = nil
|
|
}
|
|
if input.SSECustomerKey != nil && *input.SSECustomerKey == "" {
|
|
input.SSECustomerKey = nil
|
|
}
|
|
if input.SSECustomerKeyMD5 != nil && *input.SSECustomerKeyMD5 == "" {
|
|
input.SSECustomerKeyMD5 = nil
|
|
}
|
|
if input.SSEKMSEncryptionContext != nil && *input.SSEKMSEncryptionContext == "" {
|
|
input.SSEKMSEncryptionContext = nil
|
|
}
|
|
if input.SSEKMSKeyId != nil && *input.SSEKMSKeyId == "" {
|
|
input.SSEKMSKeyId = nil
|
|
}
|
|
if input.Tagging != nil && *input.Tagging == "" {
|
|
input.Tagging = nil
|
|
}
|
|
if input.WebsiteRedirectLocation != nil && *input.WebsiteRedirectLocation == "" {
|
|
input.WebsiteRedirectLocation = nil
|
|
}
|
|
|
|
var expires *time.Time
|
|
if input.Expires != nil {
|
|
exp, err := time.Parse(time.RFC1123, *input.Expires)
|
|
if err == nil {
|
|
expires = &exp
|
|
}
|
|
}
|
|
|
|
out, err := s.client.CopyObject(ctx,
|
|
&s3.CopyObjectInput{
|
|
Metadata: input.Metadata,
|
|
Bucket: input.Bucket,
|
|
CopySource: input.CopySource,
|
|
Key: input.Key,
|
|
CacheControl: input.CacheControl,
|
|
ContentDisposition: input.ContentDisposition,
|
|
ContentEncoding: input.ContentEncoding,
|
|
ContentLanguage: input.ContentLanguage,
|
|
ContentType: input.ContentType,
|
|
CopySourceIfMatch: input.CopySourceIfMatch,
|
|
CopySourceIfNoneMatch: input.CopySourceIfNoneMatch,
|
|
CopySourceSSECustomerAlgorithm: input.CopySourceSSECustomerAlgorithm,
|
|
CopySourceSSECustomerKey: input.CopySourceSSECustomerKey,
|
|
CopySourceSSECustomerKeyMD5: input.CopySourceSSECustomerKeyMD5,
|
|
ExpectedBucketOwner: input.ExpectedBucketOwner,
|
|
ExpectedSourceBucketOwner: input.ExpectedSourceBucketOwner,
|
|
Expires: expires,
|
|
GrantFullControl: input.GrantFullControl,
|
|
GrantRead: input.GrantRead,
|
|
GrantReadACP: input.GrantReadACP,
|
|
GrantWriteACP: input.GrantWriteACP,
|
|
SSECustomerAlgorithm: input.SSECustomerAlgorithm,
|
|
SSECustomerKey: input.SSECustomerKey,
|
|
SSECustomerKeyMD5: input.SSECustomerKeyMD5,
|
|
SSEKMSEncryptionContext: input.SSEKMSEncryptionContext,
|
|
SSEKMSKeyId: input.SSEKMSKeyId,
|
|
Tagging: input.Tagging,
|
|
WebsiteRedirectLocation: input.WebsiteRedirectLocation,
|
|
CopySourceIfModifiedSince: input.CopySourceIfModifiedSince,
|
|
CopySourceIfUnmodifiedSince: input.CopySourceIfUnmodifiedSince,
|
|
ObjectLockRetainUntilDate: input.ObjectLockRetainUntilDate,
|
|
BucketKeyEnabled: input.BucketKeyEnabled,
|
|
ACL: input.ACL,
|
|
ChecksumAlgorithm: input.ChecksumAlgorithm,
|
|
MetadataDirective: input.MetadataDirective,
|
|
ObjectLockLegalHoldStatus: input.ObjectLockLegalHoldStatus,
|
|
ObjectLockMode: input.ObjectLockMode,
|
|
RequestPayer: input.RequestPayer,
|
|
ServerSideEncryption: input.ServerSideEncryption,
|
|
StorageClass: input.StorageClass,
|
|
TaggingDirective: input.TaggingDirective,
|
|
})
|
|
if err != nil {
|
|
return s3response.CopyObjectOutput{}, handleError(err)
|
|
}
|
|
if out.CopyObjectResult == nil {
|
|
out.CopyObjectResult = &types.CopyObjectResult{}
|
|
}
|
|
return s3response.CopyObjectOutput{
|
|
BucketKeyEnabled: out.BucketKeyEnabled,
|
|
CopyObjectResult: &s3response.CopyObjectResult{
|
|
ChecksumCRC32: out.CopyObjectResult.ChecksumCRC32,
|
|
ChecksumCRC32C: out.CopyObjectResult.ChecksumCRC32C,
|
|
ChecksumCRC64NVME: out.CopyObjectResult.ChecksumCRC64NVME,
|
|
ChecksumSHA1: out.CopyObjectResult.ChecksumSHA1,
|
|
ChecksumSHA256: out.CopyObjectResult.ChecksumSHA256,
|
|
ChecksumType: out.CopyObjectResult.ChecksumType,
|
|
ETag: out.CopyObjectResult.ETag,
|
|
LastModified: out.CopyObjectResult.LastModified,
|
|
},
|
|
CopySourceVersionId: out.CopySourceVersionId,
|
|
Expiration: out.Expiration,
|
|
SSECustomerAlgorithm: out.SSECustomerAlgorithm,
|
|
SSECustomerKeyMD5: out.SSECustomerKeyMD5,
|
|
SSEKMSEncryptionContext: out.SSEKMSEncryptionContext,
|
|
SSEKMSKeyId: out.SSEKMSKeyId,
|
|
ServerSideEncryption: out.ServerSideEncryption,
|
|
VersionId: out.VersionId,
|
|
}, handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) ListObjects(ctx context.Context, input *s3.ListObjectsInput) (s3response.ListObjectsResult, error) {
|
|
if *input.Bucket == s.metaBucket {
|
|
return s3response.ListObjectsResult{}, s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
if input.Delimiter != nil && *input.Delimiter == "" {
|
|
input.Delimiter = nil
|
|
}
|
|
if input.ExpectedBucketOwner != nil && *input.ExpectedBucketOwner == "" {
|
|
input.ExpectedBucketOwner = nil
|
|
}
|
|
if input.Marker != nil && *input.Marker == "" {
|
|
input.Marker = nil
|
|
}
|
|
if input.MaxKeys != nil && *input.MaxKeys == 0 {
|
|
input.MaxKeys = nil
|
|
}
|
|
if input.Prefix != nil && *input.Prefix == "" {
|
|
input.Prefix = nil
|
|
}
|
|
|
|
out, err := s.client.ListObjects(ctx, input)
|
|
if err != nil {
|
|
return s3response.ListObjectsResult{}, handleError(err)
|
|
}
|
|
|
|
contents := convertObjects(out.Contents)
|
|
|
|
return s3response.ListObjectsResult{
|
|
CommonPrefixes: out.CommonPrefixes,
|
|
Contents: contents,
|
|
Delimiter: out.Delimiter,
|
|
IsTruncated: out.IsTruncated,
|
|
Marker: out.Marker,
|
|
MaxKeys: out.MaxKeys,
|
|
Name: out.Name,
|
|
NextMarker: out.NextMarker,
|
|
Prefix: out.Prefix,
|
|
}, nil
|
|
}
|
|
|
|
func (s *S3Proxy) ListObjectsV2(ctx context.Context, input *s3.ListObjectsV2Input) (s3response.ListObjectsV2Result, error) {
|
|
if *input.Bucket == s.metaBucket {
|
|
return s3response.ListObjectsV2Result{}, s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
if input.ContinuationToken != nil && *input.ContinuationToken == "" {
|
|
input.ContinuationToken = nil
|
|
}
|
|
if input.Delimiter != nil && *input.Delimiter == "" {
|
|
input.Delimiter = nil
|
|
}
|
|
if input.ExpectedBucketOwner != nil && *input.ExpectedBucketOwner == "" {
|
|
input.ExpectedBucketOwner = nil
|
|
}
|
|
if input.MaxKeys != nil && *input.MaxKeys == 0 {
|
|
input.MaxKeys = nil
|
|
}
|
|
if input.Prefix != nil && *input.Prefix == "" {
|
|
input.Prefix = nil
|
|
}
|
|
if input.StartAfter != nil && *input.StartAfter == "" {
|
|
input.StartAfter = nil
|
|
}
|
|
|
|
out, err := s.client.ListObjectsV2(ctx, input)
|
|
if err != nil {
|
|
return s3response.ListObjectsV2Result{}, handleError(err)
|
|
}
|
|
|
|
contents := convertObjects(out.Contents)
|
|
|
|
return s3response.ListObjectsV2Result{
|
|
CommonPrefixes: out.CommonPrefixes,
|
|
Contents: contents,
|
|
Delimiter: out.Delimiter,
|
|
IsTruncated: out.IsTruncated,
|
|
ContinuationToken: out.ContinuationToken,
|
|
MaxKeys: out.MaxKeys,
|
|
Name: out.Name,
|
|
NextContinuationToken: out.NextContinuationToken,
|
|
Prefix: out.Prefix,
|
|
KeyCount: out.KeyCount,
|
|
}, nil
|
|
}
|
|
|
|
func (s *S3Proxy) DeleteObject(ctx context.Context, input *s3.DeleteObjectInput) (*s3.DeleteObjectOutput, error) {
|
|
if *input.Bucket == s.metaBucket {
|
|
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
if input.ExpectedBucketOwner != nil && *input.ExpectedBucketOwner == "" {
|
|
input.ExpectedBucketOwner = nil
|
|
}
|
|
if input.IfMatch != nil && *input.IfMatch == "" {
|
|
input.IfMatch = nil
|
|
}
|
|
if input.IfMatchLastModifiedTime != nil && (*input.IfMatchLastModifiedTime).Equal(defTime) {
|
|
input.IfMatchLastModifiedTime = nil
|
|
}
|
|
if input.IfMatchSize != nil && *input.IfMatchSize == 0 {
|
|
input.IfMatchSize = nil
|
|
}
|
|
if input.MFA != nil && *input.MFA == "" {
|
|
input.MFA = nil
|
|
}
|
|
if input.VersionId != nil && *input.VersionId == "" {
|
|
input.VersionId = nil
|
|
}
|
|
|
|
res, err := s.client.DeleteObject(ctx, input)
|
|
return res, handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) DeleteObjects(ctx context.Context, input *s3.DeleteObjectsInput) (s3response.DeleteResult, error) {
|
|
if *input.Bucket == s.metaBucket {
|
|
return s3response.DeleteResult{}, s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
if input.ExpectedBucketOwner != nil && *input.ExpectedBucketOwner == "" {
|
|
input.ExpectedBucketOwner = nil
|
|
}
|
|
if input.MFA != nil && *input.MFA == "" {
|
|
input.MFA = nil
|
|
}
|
|
|
|
if len(input.Delete.Objects) == 0 {
|
|
input.Delete.Objects = []types.ObjectIdentifier{}
|
|
}
|
|
|
|
output, err := s.client.DeleteObjects(ctx, input)
|
|
if err != nil {
|
|
return s3response.DeleteResult{}, handleError(err)
|
|
}
|
|
|
|
return s3response.DeleteResult{
|
|
Deleted: output.Deleted,
|
|
Error: output.Errors,
|
|
}, nil
|
|
}
|
|
|
|
func (s *S3Proxy) GetBucketAcl(ctx context.Context, input *s3.GetBucketAclInput) ([]byte, error) {
|
|
data, err := s.getMetaBucketObjData(ctx, *input.Bucket, metaPrefixAcl, false)
|
|
if err != nil {
|
|
return nil, handleError(err)
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
func (s *S3Proxy) PutBucketAcl(ctx context.Context, bucket string, data []byte) error {
|
|
return handleError(s.putMetaBucketObj(ctx, bucket, data, metaPrefixAcl))
|
|
}
|
|
|
|
func (s *S3Proxy) PutObjectTagging(ctx context.Context, bucket, object, versionId string, tags map[string]string) error {
|
|
if bucket == s.metaBucket {
|
|
return s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
tagging := &types.Tagging{
|
|
TagSet: []types.Tag{},
|
|
}
|
|
for key, val := range tags {
|
|
tagging.TagSet = append(tagging.TagSet, types.Tag{
|
|
Key: &key,
|
|
Value: &val,
|
|
})
|
|
}
|
|
|
|
_, err := s.client.PutObjectTagging(ctx, &s3.PutObjectTaggingInput{
|
|
Bucket: &bucket,
|
|
Key: &object,
|
|
VersionId: &versionId,
|
|
Tagging: tagging,
|
|
})
|
|
return handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) GetObjectTagging(ctx context.Context, bucket, object, versionId string) (map[string]string, error) {
|
|
if bucket == s.metaBucket {
|
|
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
output, err := s.client.GetObjectTagging(ctx, &s3.GetObjectTaggingInput{
|
|
Bucket: &bucket,
|
|
Key: &object,
|
|
VersionId: &versionId,
|
|
})
|
|
if err != nil {
|
|
return nil, handleError(err)
|
|
}
|
|
|
|
tags := make(map[string]string)
|
|
for _, el := range output.TagSet {
|
|
tags[*el.Key] = *el.Value
|
|
}
|
|
|
|
return tags, nil
|
|
}
|
|
|
|
func (s *S3Proxy) DeleteObjectTagging(ctx context.Context, bucket, object, versionId string) error {
|
|
if bucket == s.metaBucket {
|
|
return s3err.GetAPIError(s3err.ErrAccessDenied)
|
|
}
|
|
_, err := s.client.DeleteObjectTagging(ctx, &s3.DeleteObjectTaggingInput{
|
|
Bucket: &bucket,
|
|
Key: &object,
|
|
VersionId: &versionId,
|
|
})
|
|
return handleError(err)
|
|
}
|
|
|
|
func (s *S3Proxy) PutBucketCors(ctx context.Context, bucket string, cors []byte) error {
|
|
return handleError(s.putMetaBucketObj(ctx, bucket, cors, metaPrefixCors))
|
|
}
|
|
|
|
func (s *S3Proxy) GetBucketCors(ctx context.Context, bucket string) ([]byte, error) {
|
|
data, err := s.getMetaBucketObjData(ctx, bucket, metaPrefixCors, false)
|
|
if err != nil {
|
|
return nil, handleError(err)
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
func (s *S3Proxy) DeleteBucketCors(ctx context.Context, bucket string) error {
|
|
key := getMetaKey(bucket, metaPrefixCors)
|
|
_, err := s.client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &s.metaBucket,
|
|
Key: &key,
|
|
})
|
|
if err != nil && !areErrSame(err, s3err.GetAPIError(s3err.ErrNoSuchKey)) {
|
|
return handleError(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *S3Proxy) PutBucketPolicy(ctx context.Context, bucket string, policy []byte) error {
|
|
return handleError(s.putMetaBucketObj(ctx, bucket, policy, metaPrefixPolicy))
|
|
}
|
|
|
|
func (s *S3Proxy) GetBucketPolicy(ctx context.Context, bucket string) ([]byte, error) {
|
|
data, err := s.getMetaBucketObjData(ctx, bucket, metaPrefixPolicy, false)
|
|
if err != nil {
|
|
return nil, handleError(err)
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
func (s *S3Proxy) DeleteBucketPolicy(ctx context.Context, bucket string) error {
|
|
key := getMetaKey(bucket, metaPrefixPolicy)
|
|
_, err := s.client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
Bucket: &s.metaBucket,
|
|
Key: &key,
|
|
})
|
|
if err != nil && !areErrSame(err, s3err.GetAPIError(s3err.ErrNoSuchKey)) {
|
|
return handleError(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *S3Proxy) PutObjectLockConfiguration(ctx context.Context, bucket string, config []byte) error {
|
|
return s3err.GetAPIError(s3err.ErrNotImplemented)
|
|
}
|
|
|
|
func (s *S3Proxy) GetObjectLockConfiguration(ctx context.Context, bucket string) ([]byte, error) {
|
|
return nil, s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotFound)
|
|
}
|
|
|
|
func (s *S3Proxy) PutObjectRetention(ctx context.Context, bucket, object, versionId string, retention []byte) error {
|
|
return s3err.GetAPIError(s3err.ErrNotImplemented)
|
|
}
|
|
|
|
func (s *S3Proxy) GetObjectRetention(ctx context.Context, bucket, object, versionId string) ([]byte, error) {
|
|
return nil, s3err.GetAPIError(s3err.ErrNotImplemented)
|
|
|
|
}
|
|
|
|
func (s *S3Proxy) PutObjectLegalHold(ctx context.Context, bucket, object, versionId string, status bool) error {
|
|
return s3err.GetAPIError(s3err.ErrNotImplemented)
|
|
}
|
|
|
|
func (s *S3Proxy) GetObjectLegalHold(ctx context.Context, bucket, object, versionId string) (*bool, error) {
|
|
return nil, s3err.GetAPIError(s3err.ErrNotImplemented)
|
|
}
|
|
|
|
func (s *S3Proxy) ChangeBucketOwner(ctx context.Context, bucket, owner string) error {
|
|
return auth.UpdateBucketACLOwner(ctx, s, bucket, owner)
|
|
}
|
|
|
|
func (s *S3Proxy) ListBucketsAndOwners(ctx context.Context) ([]s3response.Bucket, error) {
|
|
var buckets []s3response.Bucket
|
|
|
|
paginator := s3.NewListBucketsPaginator(s.client, &s3.ListBucketsInput{})
|
|
|
|
for paginator.HasMorePages() {
|
|
page, err := paginator.NextPage(ctx)
|
|
if err != nil {
|
|
return nil, handleError(err)
|
|
}
|
|
for _, bucket := range page.Buckets {
|
|
if *bucket.Name == s.metaBucket {
|
|
continue
|
|
}
|
|
aclJSON, err := s.getMetaBucketObjData(ctx, *bucket.Name, metaPrefixAcl, false)
|
|
if err != nil {
|
|
return nil, handleError(err)
|
|
}
|
|
|
|
acl, err := auth.ParseACL(aclJSON)
|
|
if err != nil {
|
|
return buckets, fmt.Errorf("parse acl tag: %w", err)
|
|
}
|
|
|
|
buckets = append(buckets, s3response.Bucket{
|
|
Name: *bucket.Name,
|
|
Owner: acl.Owner,
|
|
})
|
|
}
|
|
}
|
|
|
|
return buckets, nil
|
|
}
|
|
|
|
func (s *S3Proxy) bucketExists(ctx context.Context, bucket string) bool {
|
|
_, err := s.client.HeadBucket(ctx, &s3.HeadBucketInput{
|
|
Bucket: &bucket,
|
|
})
|
|
return err == nil
|
|
}
|
|
|
|
func (s *S3Proxy) putMetaBucketObj(ctx context.Context, bucket string, data []byte, prefix metaPrefix) error {
|
|
// if meta bucket is not provided, return successful response
|
|
if s.metaBucket == "" {
|
|
return nil
|
|
}
|
|
|
|
key := getMetaKey(bucket, prefix)
|
|
// store the provided bucket acl/policy as an object in meta bucket
|
|
_, err := s.client.PutObject(ctx, &s3.PutObjectInput{
|
|
Bucket: &s.metaBucket,
|
|
Key: &key,
|
|
Body: bytes.NewReader(data),
|
|
})
|
|
return err
|
|
}
|
|
|
|
// set checkExists to true if using to check for existence of bucket, in
|
|
// this case it will not return default acl/policy if the metadata does
|
|
// not exist
|
|
func (s *S3Proxy) getMetaBucketObjData(ctx context.Context, bucket string, prefix metaPrefix, checkExists bool) ([]byte, error) {
|
|
// return default bahviour of get bucket policy/acl, if meta bucket is not provided
|
|
if s.metaBucket == "" {
|
|
return handleMetaBucketObjectNotFoundErr(prefix)
|
|
}
|
|
|
|
key := getMetaKey(bucket, prefix)
|
|
// get meta bucket object
|
|
res, err := s.client.GetObject(ctx, &s3.GetObjectInput{
|
|
Bucket: &s.metaBucket,
|
|
Key: &key,
|
|
})
|
|
if areErrSame(err, s3err.GetAPIError(s3err.ErrNoSuchKey)) {
|
|
if checkExists {
|
|
return nil, err
|
|
}
|
|
|
|
return handleMetaBucketObjectNotFoundErr(prefix)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
data, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read meta object data: %w", err)
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
// handles the case when an object with the given metprefix
|
|
// is not found in meta bucket. Aggregates the not found errors
|
|
// for each meta prefix
|
|
func handleMetaBucketObjectNotFoundErr(prefix metaPrefix) ([]byte, error) {
|
|
switch prefix {
|
|
case metaPrefixAcl:
|
|
// If bucket acl is not found, return default acl
|
|
return []byte{}, nil
|
|
case metaPrefixPolicy:
|
|
return nil, s3err.GetAPIError(s3err.ErrNoSuchBucketPolicy)
|
|
case metaPrefixCors:
|
|
return nil, s3err.GetAPIError(s3err.ErrNoSuchCORSConfiguration)
|
|
}
|
|
|
|
return []byte{}, nil
|
|
}
|
|
|
|
// Checks if the provided err is a type of smithy.APIError
|
|
// and if the error code and message match with the provided apiErr
|
|
func areErrSame(err error, apiErr s3err.APIError) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
var ae smithy.APIError
|
|
if errors.As(err, &ae) {
|
|
if ae.ErrorCode() != apiErr.Code {
|
|
return false
|
|
}
|
|
|
|
// 404 errors are not well serialized by aws-sdk-go-v2
|
|
if ae.ErrorCode() != "NoSuchKey" && ae.ErrorMessage() != apiErr.Description {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// generates meta object key with bucket name and meta prefix
|
|
func getMetaKey(bucket string, prefix metaPrefix) string {
|
|
return string(prefix) + bucket
|
|
}
|
|
|
|
func handleError(err error) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
var ae smithy.APIError
|
|
if errors.As(err, &ae) {
|
|
apiErr := s3err.APIError{
|
|
Code: ae.ErrorCode(),
|
|
Description: ae.ErrorMessage(),
|
|
}
|
|
var re *awshttp.ResponseError
|
|
if errors.As(err, &re) {
|
|
apiErr.HTTPStatusCode = re.Response.StatusCode
|
|
}
|
|
return apiErr
|
|
}
|
|
return err
|
|
}
|
|
|
|
func convertObjects(objs []types.Object) []s3response.Object {
|
|
result := make([]s3response.Object, 0, len(objs))
|
|
|
|
for _, obj := range objs {
|
|
result = append(result, s3response.Object{
|
|
ETag: obj.ETag,
|
|
Key: obj.Key,
|
|
LastModified: obj.LastModified,
|
|
Owner: obj.Owner,
|
|
Size: obj.Size,
|
|
RestoreStatus: obj.RestoreStatus,
|
|
StorageClass: obj.StorageClass,
|
|
ChecksumAlgorithm: obj.ChecksumAlgorithm,
|
|
ChecksumType: obj.ChecksumType,
|
|
})
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func convertObjectVersions(versions []types.ObjectVersion) []s3response.ObjectVersion {
|
|
result := make([]s3response.ObjectVersion, 0, len(versions))
|
|
for _, v := range versions {
|
|
result = append(result, s3response.ObjectVersion{
|
|
ChecksumAlgorithm: v.ChecksumAlgorithm,
|
|
ChecksumType: v.ChecksumType,
|
|
ETag: v.ETag,
|
|
IsLatest: v.IsLatest,
|
|
Key: v.Key,
|
|
LastModified: v.LastModified,
|
|
Owner: v.Owner,
|
|
RestoreStatus: v.RestoreStatus,
|
|
Size: v.Size,
|
|
StorageClass: v.StorageClass,
|
|
VersionId: v.VersionId,
|
|
})
|
|
}
|
|
|
|
return result
|
|
}
|