mirror of
https://github.com/versity/versitygw.git
synced 2025-12-23 13:15:18 +00:00
fix: Changes GetObjectAttributes action xml encoding root element to GetObjectAttributesResponse. Adds input validation for x-amz-object-attributes header. Adds x-amz-delete-marker and x-maz-version-id headers for GetObjectAttributes action. Adds VersionId in HeadObject response, if it's not specified in the request
This commit is contained in:
@@ -512,20 +512,22 @@ func (az *Azure) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (az *Azure) GetObjectAttributes(ctx context.Context, input *s3.GetObjectAttributesInput) (s3response.GetObjectAttributesResult, error) {
|
func (az *Azure) GetObjectAttributes(ctx context.Context, input *s3.GetObjectAttributesInput) (s3response.GetObjectAttributesResponse, error) {
|
||||||
data, err := az.HeadObject(ctx, &s3.HeadObjectInput{
|
data, err := az.HeadObject(ctx, &s3.HeadObjectInput{
|
||||||
Bucket: input.Bucket,
|
Bucket: input.Bucket,
|
||||||
Key: input.Key,
|
Key: input.Key,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s3response.GetObjectAttributesResult{}, err
|
return s3response.GetObjectAttributesResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s3response.GetObjectAttributesResult{
|
return s3response.GetObjectAttributesResponse{
|
||||||
ETag: data.ETag,
|
ETag: data.ETag,
|
||||||
LastModified: data.LastModified,
|
|
||||||
ObjectSize: data.ContentLength,
|
ObjectSize: data.ContentLength,
|
||||||
StorageClass: data.StorageClass,
|
StorageClass: data.StorageClass,
|
||||||
|
LastModified: data.LastModified,
|
||||||
|
VersionId: data.VersionId,
|
||||||
|
DeleteMarker: data.DeleteMarker,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ type Backend interface {
|
|||||||
HeadObject(context.Context, *s3.HeadObjectInput) (*s3.HeadObjectOutput, error)
|
HeadObject(context.Context, *s3.HeadObjectInput) (*s3.HeadObjectOutput, error)
|
||||||
GetObject(context.Context, *s3.GetObjectInput) (*s3.GetObjectOutput, error)
|
GetObject(context.Context, *s3.GetObjectInput) (*s3.GetObjectOutput, error)
|
||||||
GetObjectAcl(context.Context, *s3.GetObjectAclInput) (*s3.GetObjectAclOutput, error)
|
GetObjectAcl(context.Context, *s3.GetObjectAclInput) (*s3.GetObjectAclOutput, error)
|
||||||
GetObjectAttributes(context.Context, *s3.GetObjectAttributesInput) (s3response.GetObjectAttributesResult, error)
|
GetObjectAttributes(context.Context, *s3.GetObjectAttributesInput) (s3response.GetObjectAttributesResponse, error)
|
||||||
CopyObject(context.Context, *s3.CopyObjectInput) (*s3.CopyObjectOutput, error)
|
CopyObject(context.Context, *s3.CopyObjectInput) (*s3.CopyObjectOutput, error)
|
||||||
ListObjects(context.Context, *s3.ListObjectsInput) (s3response.ListObjectsResult, error)
|
ListObjects(context.Context, *s3.ListObjectsInput) (s3response.ListObjectsResult, error)
|
||||||
ListObjectsV2(context.Context, *s3.ListObjectsV2Input) (s3response.ListObjectsV2Result, error)
|
ListObjectsV2(context.Context, *s3.ListObjectsV2Input) (s3response.ListObjectsV2Result, error)
|
||||||
@@ -185,8 +185,8 @@ func (BackendUnsupported) GetObject(context.Context, *s3.GetObjectInput) (*s3.Ge
|
|||||||
func (BackendUnsupported) GetObjectAcl(context.Context, *s3.GetObjectAclInput) (*s3.GetObjectAclOutput, error) {
|
func (BackendUnsupported) GetObjectAcl(context.Context, *s3.GetObjectAclInput) (*s3.GetObjectAclOutput, error) {
|
||||||
return nil, s3err.GetAPIError(s3err.ErrNotImplemented)
|
return nil, s3err.GetAPIError(s3err.ErrNotImplemented)
|
||||||
}
|
}
|
||||||
func (BackendUnsupported) GetObjectAttributes(context.Context, *s3.GetObjectAttributesInput) (s3response.GetObjectAttributesResult, error) {
|
func (BackendUnsupported) GetObjectAttributes(context.Context, *s3.GetObjectAttributesInput) (s3response.GetObjectAttributesResponse, error) {
|
||||||
return s3response.GetObjectAttributesResult{}, s3err.GetAPIError(s3err.ErrNotImplemented)
|
return s3response.GetObjectAttributesResponse{}, s3err.GetAPIError(s3err.ErrNotImplemented)
|
||||||
}
|
}
|
||||||
func (BackendUnsupported) CopyObject(context.Context, *s3.CopyObjectInput) (*s3.CopyObjectOutput, error) {
|
func (BackendUnsupported) CopyObject(context.Context, *s3.CopyObjectInput) (*s3.CopyObjectOutput, error) {
|
||||||
return nil, s3err.GetAPIError(s3err.ErrNotImplemented)
|
return nil, s3err.GetAPIError(s3err.ErrNotImplemented)
|
||||||
|
|||||||
@@ -2961,8 +2961,9 @@ func (p *Posix) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3.
|
|||||||
if input.Key == nil {
|
if input.Key == nil {
|
||||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
|
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||||
}
|
}
|
||||||
|
versionId := backend.GetStringFromPtr(input.VersionId)
|
||||||
|
|
||||||
if !p.versioningEnabled() && *input.VersionId != "" {
|
if !p.versioningEnabled() && versionId != "" {
|
||||||
//TODO: Maybe we need to return our custom error here?
|
//TODO: Maybe we need to return our custom error here?
|
||||||
return nil, s3err.GetAPIError(s3err.ErrInvalidVersionId)
|
return nil, s3err.GetAPIError(s3err.ErrInvalidVersionId)
|
||||||
}
|
}
|
||||||
@@ -3022,7 +3023,7 @@ func (p *Posix) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3.
|
|||||||
return nil, fmt.Errorf("stat bucket: %w", err)
|
return nil, fmt.Errorf("stat bucket: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *input.VersionId != "" {
|
if versionId != "" {
|
||||||
vId, err := p.meta.RetrieveAttribute(nil, bucket, object, versionIdKey)
|
vId, err := p.meta.RetrieveAttribute(nil, bucket, object, versionIdKey)
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
|
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||||
@@ -3032,12 +3033,12 @@ func (p *Posix) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3.
|
|||||||
}
|
}
|
||||||
if errors.Is(err, meta.ErrNoSuchKey) {
|
if errors.Is(err, meta.ErrNoSuchKey) {
|
||||||
bucket = filepath.Join(p.versioningDir, bucket)
|
bucket = filepath.Join(p.versioningDir, bucket)
|
||||||
object = filepath.Join(genObjVersionKey(object), *input.VersionId)
|
object = filepath.Join(genObjVersionKey(object), versionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if string(vId) != *input.VersionId {
|
if string(vId) != versionId {
|
||||||
bucket = filepath.Join(p.versioningDir, bucket)
|
bucket = filepath.Join(p.versioningDir, bucket)
|
||||||
object = filepath.Join(genObjVersionKey(object), *input.VersionId)
|
object = filepath.Join(genObjVersionKey(object), versionId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3045,7 +3046,7 @@ func (p *Posix) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3.
|
|||||||
|
|
||||||
fi, err := os.Stat(objPath)
|
fi, err := os.Stat(objPath)
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
if *input.VersionId != "" {
|
if versionId != "" {
|
||||||
return nil, s3err.GetAPIError(s3err.ErrInvalidVersionId)
|
return nil, s3err.GetAPIError(s3err.ErrInvalidVersionId)
|
||||||
}
|
}
|
||||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
|
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||||
@@ -3063,7 +3064,7 @@ func (p *Posix) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3.
|
|||||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
|
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *input.VersionId != "" {
|
if p.versioningEnabled() {
|
||||||
isDelMarker, err := p.isObjDeleteMarker(bucket, object)
|
isDelMarker, err := p.isObjDeleteMarker(bucket, object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -3078,6 +3079,15 @@ func (p *Posix) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.versioningEnabled() && versionId == "" {
|
||||||
|
vId, err := p.meta.RetrieveAttribute(nil, bucket, object, versionIdKey)
|
||||||
|
if err != nil && !errors.Is(err, meta.ErrNoSuchKey) {
|
||||||
|
return nil, fmt.Errorf("get object versionId: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
versionId = string(vId)
|
||||||
|
}
|
||||||
|
|
||||||
userMetaData := make(map[string]string)
|
userMetaData := make(map[string]string)
|
||||||
contentType, contentEncoding := p.loadUserMetaData(bucket, object, userMetaData)
|
contentType, contentEncoding := p.loadUserMetaData(bucket, object, userMetaData)
|
||||||
|
|
||||||
@@ -3094,7 +3104,7 @@ func (p *Posix) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3.
|
|||||||
size := fi.Size()
|
size := fi.Size()
|
||||||
|
|
||||||
var objectLockLegalHoldStatus types.ObjectLockLegalHoldStatus
|
var objectLockLegalHoldStatus types.ObjectLockLegalHoldStatus
|
||||||
status, err := p.GetObjectLegalHold(ctx, bucket, object, *input.VersionId)
|
status, err := p.GetObjectLegalHold(ctx, bucket, object, versionId)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if *status {
|
if *status {
|
||||||
objectLockLegalHoldStatus = types.ObjectLockLegalHoldStatusOn
|
objectLockLegalHoldStatus = types.ObjectLockLegalHoldStatusOn
|
||||||
@@ -3105,7 +3115,7 @@ func (p *Posix) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3.
|
|||||||
|
|
||||||
var objectLockMode types.ObjectLockMode
|
var objectLockMode types.ObjectLockMode
|
||||||
var objectLockRetainUntilDate *time.Time
|
var objectLockRetainUntilDate *time.Time
|
||||||
retention, err := p.GetObjectRetention(ctx, bucket, object, *input.VersionId)
|
retention, err := p.GetObjectRetention(ctx, bucket, object, versionId)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
var config types.ObjectLockRetention
|
var config types.ObjectLockRetention
|
||||||
if err := json.Unmarshal(retention, &config); err == nil {
|
if err := json.Unmarshal(retention, &config); err == nil {
|
||||||
@@ -3114,8 +3124,6 @@ func (p *Posix) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: the method must handle multipart upload case
|
|
||||||
|
|
||||||
return &s3.HeadObjectOutput{
|
return &s3.HeadObjectOutput{
|
||||||
ContentLength: &size,
|
ContentLength: &size,
|
||||||
ContentType: &contentType,
|
ContentType: &contentType,
|
||||||
@@ -3127,25 +3135,34 @@ func (p *Posix) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3.
|
|||||||
ObjectLockMode: objectLockMode,
|
ObjectLockMode: objectLockMode,
|
||||||
ObjectLockRetainUntilDate: objectLockRetainUntilDate,
|
ObjectLockRetainUntilDate: objectLockRetainUntilDate,
|
||||||
StorageClass: types.StorageClassStandard,
|
StorageClass: types.StorageClassStandard,
|
||||||
VersionId: input.VersionId,
|
VersionId: &versionId,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Posix) GetObjectAttributes(ctx context.Context, input *s3.GetObjectAttributesInput) (s3response.GetObjectAttributesResult, error) {
|
func (p *Posix) GetObjectAttributes(ctx context.Context, input *s3.GetObjectAttributesInput) (s3response.GetObjectAttributesResponse, error) {
|
||||||
data, err := p.HeadObject(ctx, &s3.HeadObjectInput{
|
data, err := p.HeadObject(ctx, &s3.HeadObjectInput{
|
||||||
Bucket: input.Bucket,
|
Bucket: input.Bucket,
|
||||||
Key: input.Key,
|
Key: input.Key,
|
||||||
VersionId: input.VersionId,
|
VersionId: input.VersionId,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s3response.GetObjectAttributesResult{}, nil
|
if errors.Is(err, s3err.GetAPIError(s3err.ErrMethodNotAllowed)) && data != nil {
|
||||||
|
return s3response.GetObjectAttributesResponse{
|
||||||
|
DeleteMarker: data.DeleteMarker,
|
||||||
|
VersionId: data.VersionId,
|
||||||
|
}, s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s3response.GetObjectAttributesResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s3response.GetObjectAttributesResult{
|
return s3response.GetObjectAttributesResponse{
|
||||||
ETag: data.ETag,
|
ETag: data.ETag,
|
||||||
LastModified: data.LastModified,
|
|
||||||
ObjectSize: data.ContentLength,
|
ObjectSize: data.ContentLength,
|
||||||
StorageClass: data.StorageClass,
|
StorageClass: data.StorageClass,
|
||||||
|
LastModified: data.LastModified,
|
||||||
|
VersionId: data.VersionId,
|
||||||
|
DeleteMarker: data.DeleteMarker,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -392,7 +392,7 @@ func (s *S3Proxy) GetObject(ctx context.Context, input *s3.GetObjectInput) (*s3.
|
|||||||
return output, nil
|
return output, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *S3Proxy) GetObjectAttributes(ctx context.Context, input *s3.GetObjectAttributesInput) (s3response.GetObjectAttributesResult, error) {
|
func (s *S3Proxy) GetObjectAttributes(ctx context.Context, input *s3.GetObjectAttributesInput) (s3response.GetObjectAttributesResponse, error) {
|
||||||
out, err := s.client.GetObjectAttributes(ctx, input)
|
out, err := s.client.GetObjectAttributes(ctx, input)
|
||||||
|
|
||||||
parts := s3response.ObjectParts{}
|
parts := s3response.ObjectParts{}
|
||||||
@@ -419,12 +419,11 @@ func (s *S3Proxy) GetObjectAttributes(ctx context.Context, input *s3.GetObjectAt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return s3response.GetObjectAttributesResult{
|
return s3response.GetObjectAttributesResponse{
|
||||||
ETag: out.ETag,
|
ETag: out.ETag,
|
||||||
LastModified: out.LastModified,
|
LastModified: out.LastModified,
|
||||||
ObjectSize: out.ObjectSize,
|
ObjectSize: out.ObjectSize,
|
||||||
StorageClass: out.StorageClass,
|
StorageClass: out.StorageClass,
|
||||||
VersionId: out.VersionId,
|
|
||||||
ObjectParts: &parts,
|
ObjectParts: &parts,
|
||||||
}, handleError(err)
|
}, handleError(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ var _ backend.Backend = &BackendMock{}
|
|||||||
// GetObjectAclFunc: func(contextMoqParam context.Context, getObjectAclInput *s3.GetObjectAclInput) (*s3.GetObjectAclOutput, error) {
|
// GetObjectAclFunc: func(contextMoqParam context.Context, getObjectAclInput *s3.GetObjectAclInput) (*s3.GetObjectAclOutput, error) {
|
||||||
// panic("mock out the GetObjectAcl method")
|
// panic("mock out the GetObjectAcl method")
|
||||||
// },
|
// },
|
||||||
// GetObjectAttributesFunc: func(contextMoqParam context.Context, getObjectAttributesInput *s3.GetObjectAttributesInput) (s3response.GetObjectAttributesResult, error) {
|
// GetObjectAttributesFunc: func(contextMoqParam context.Context, getObjectAttributesInput *s3.GetObjectAttributesInput) (s3response.GetObjectAttributesResponse, error) {
|
||||||
// panic("mock out the GetObjectAttributes method")
|
// panic("mock out the GetObjectAttributes method")
|
||||||
// },
|
// },
|
||||||
// GetObjectLegalHoldFunc: func(contextMoqParam context.Context, bucket string, object string, versionId string) (*bool, error) {
|
// GetObjectLegalHoldFunc: func(contextMoqParam context.Context, bucket string, object string, versionId string) (*bool, error) {
|
||||||
@@ -244,7 +244,7 @@ type BackendMock struct {
|
|||||||
GetObjectAclFunc func(contextMoqParam context.Context, getObjectAclInput *s3.GetObjectAclInput) (*s3.GetObjectAclOutput, error)
|
GetObjectAclFunc func(contextMoqParam context.Context, getObjectAclInput *s3.GetObjectAclInput) (*s3.GetObjectAclOutput, error)
|
||||||
|
|
||||||
// GetObjectAttributesFunc mocks the GetObjectAttributes method.
|
// GetObjectAttributesFunc mocks the GetObjectAttributes method.
|
||||||
GetObjectAttributesFunc func(contextMoqParam context.Context, getObjectAttributesInput *s3.GetObjectAttributesInput) (s3response.GetObjectAttributesResult, error)
|
GetObjectAttributesFunc func(contextMoqParam context.Context, getObjectAttributesInput *s3.GetObjectAttributesInput) (s3response.GetObjectAttributesResponse, error)
|
||||||
|
|
||||||
// GetObjectLegalHoldFunc mocks the GetObjectLegalHold method.
|
// GetObjectLegalHoldFunc mocks the GetObjectLegalHold method.
|
||||||
GetObjectLegalHoldFunc func(contextMoqParam context.Context, bucket string, object string, versionId string) (*bool, error)
|
GetObjectLegalHoldFunc func(contextMoqParam context.Context, bucket string, object string, versionId string) (*bool, error)
|
||||||
@@ -1518,7 +1518,7 @@ func (mock *BackendMock) GetObjectAclCalls() []struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetObjectAttributes calls GetObjectAttributesFunc.
|
// GetObjectAttributes calls GetObjectAttributesFunc.
|
||||||
func (mock *BackendMock) GetObjectAttributes(contextMoqParam context.Context, getObjectAttributesInput *s3.GetObjectAttributesInput) (s3response.GetObjectAttributesResult, error) {
|
func (mock *BackendMock) GetObjectAttributes(contextMoqParam context.Context, getObjectAttributesInput *s3.GetObjectAttributesInput) (s3response.GetObjectAttributesResponse, error) {
|
||||||
if mock.GetObjectAttributesFunc == nil {
|
if mock.GetObjectAttributesFunc == nil {
|
||||||
panic("BackendMock.GetObjectAttributesFunc: method is nil but Backend.GetObjectAttributes was just called")
|
panic("BackendMock.GetObjectAttributesFunc: method is nil but Backend.GetObjectAttributes was just called")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,8 +51,9 @@ type S3ApiController struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
iso8601Format = "20060102T150405Z"
|
iso8601Format = "20060102T150405Z"
|
||||||
defaultContentType = "binary/octet-stream"
|
iso8601TimeFormatExtended = "Mon Jan _2 15:04:05 2006"
|
||||||
|
defaultContentType = "binary/octet-stream"
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(be backend.Backend, iam auth.IAMService, logger s3log.AuditLogger, evs s3event.S3EventSender, mm *metrics.Manager, debug bool, readonly bool) S3ApiController {
|
func New(be backend.Backend, iam auth.IAMService, logger s3log.AuditLogger, evs s3event.S3EventSender, mm *metrics.Manager, debug bool, readonly bool) S3ApiController {
|
||||||
@@ -379,7 +380,19 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error {
|
|||||||
BucketOwner: parsedAcl.Owner,
|
BucketOwner: parsedAcl.Owner,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
attrs := utils.ParseObjectAttributes(ctx)
|
attrs, err := utils.ParseObjectAttributes(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if c.debug {
|
||||||
|
log.Printf("error parsing object attributes: %v", err)
|
||||||
|
}
|
||||||
|
return SendXMLResponse(ctx, nil, err,
|
||||||
|
&MetaOpts{
|
||||||
|
Logger: c.logger,
|
||||||
|
MetricsMng: c.mm,
|
||||||
|
Action: metrics.ActionGetObjectAttributes,
|
||||||
|
BucketOwner: parsedAcl.Owner,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
res, err := c.be.GetObjectAttributes(ctx.Context(),
|
res, err := c.be.GetObjectAttributes(ctx.Context(),
|
||||||
&s3.GetObjectAttributesInput{
|
&s3.GetObjectAttributesInput{
|
||||||
@@ -390,6 +403,22 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error {
|
|||||||
VersionId: &versionId,
|
VersionId: &versionId,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
hdrs := []utils.CustomHeader{}
|
||||||
|
|
||||||
|
if res.DeleteMarker != nil {
|
||||||
|
hdrs = append(hdrs, utils.CustomHeader{
|
||||||
|
Key: "x-amz-delete-marker",
|
||||||
|
Value: "true",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if getstring(res.VersionId) != "" {
|
||||||
|
hdrs = append(hdrs, utils.CustomHeader{
|
||||||
|
Key: "x-amz-version-id",
|
||||||
|
Value: getstring(res.VersionId),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SetResponseHeaders(ctx, hdrs)
|
||||||
return SendXMLResponse(ctx, nil, err,
|
return SendXMLResponse(ctx, nil, err,
|
||||||
&MetaOpts{
|
&MetaOpts{
|
||||||
Logger: c.logger,
|
Logger: c.logger,
|
||||||
@@ -398,7 +427,31 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error {
|
|||||||
BucketOwner: parsedAcl.Owner,
|
BucketOwner: parsedAcl.Owner,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return SendXMLResponse(ctx, utils.FilterObjectAttributes(attrs, res), err,
|
|
||||||
|
hdrs := []utils.CustomHeader{}
|
||||||
|
|
||||||
|
if getstring(res.VersionId) != "" {
|
||||||
|
hdrs = append(hdrs, utils.CustomHeader{
|
||||||
|
Key: "x-amz-version-id",
|
||||||
|
Value: getstring(res.VersionId),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if res.DeleteMarker != nil && *res.DeleteMarker {
|
||||||
|
hdrs = append(hdrs, utils.CustomHeader{
|
||||||
|
Key: "x-amz-delete-marker",
|
||||||
|
Value: "true",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if res.LastModified != nil {
|
||||||
|
hdrs = append(hdrs, utils.CustomHeader{
|
||||||
|
Key: "Last-Modified",
|
||||||
|
Value: res.LastModified.UTC().Format(iso8601TimeFormatExtended),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SetResponseHeaders(ctx, hdrs)
|
||||||
|
|
||||||
|
return SendXMLResponse(ctx, utils.FilterObjectAttributes(attrs, res), nil,
|
||||||
&MetaOpts{
|
&MetaOpts{
|
||||||
Logger: c.logger,
|
Logger: c.logger,
|
||||||
MetricsMng: c.mm,
|
MetricsMng: c.mm,
|
||||||
@@ -2900,15 +2953,6 @@ func (c S3ApiController) HeadObject(ctx *fiber.Ctx) error {
|
|||||||
BucketOwner: parsedAcl.Owner,
|
BucketOwner: parsedAcl.Owner,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if res == nil {
|
|
||||||
return SendResponse(ctx, fmt.Errorf("head object nil response"),
|
|
||||||
&MetaOpts{
|
|
||||||
Logger: c.logger,
|
|
||||||
MetricsMng: c.mm,
|
|
||||||
Action: metrics.ActionHeadObject,
|
|
||||||
BucketOwner: parsedAcl.Owner,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
utils.SetMetaHeaders(ctx, res.Metadata)
|
utils.SetMetaHeaders(ctx, res.Metadata)
|
||||||
headers := []utils.CustomHeader{
|
headers := []utils.CustomHeader{
|
||||||
|
|||||||
@@ -187,8 +187,8 @@ func TestS3ApiController_GetActions(t *testing.T) {
|
|||||||
GetObjectAclFunc: func(context.Context, *s3.GetObjectAclInput) (*s3.GetObjectAclOutput, error) {
|
GetObjectAclFunc: func(context.Context, *s3.GetObjectAclInput) (*s3.GetObjectAclOutput, error) {
|
||||||
return &s3.GetObjectAclOutput{}, nil
|
return &s3.GetObjectAclOutput{}, nil
|
||||||
},
|
},
|
||||||
GetObjectAttributesFunc: func(context.Context, *s3.GetObjectAttributesInput) (s3response.GetObjectAttributesResult, error) {
|
GetObjectAttributesFunc: func(context.Context, *s3.GetObjectAttributesInput) (s3response.GetObjectAttributesResponse, error) {
|
||||||
return s3response.GetObjectAttributesResult{}, nil
|
return s3response.GetObjectAttributesResponse{}, nil
|
||||||
},
|
},
|
||||||
GetObjectFunc: func(context.Context, *s3.GetObjectInput) (*s3.GetObjectOutput, error) {
|
GetObjectFunc: func(context.Context, *s3.GetObjectInput) (*s3.GetObjectOutput, error) {
|
||||||
return &s3.GetObjectOutput{
|
return &s3.GetObjectOutput{
|
||||||
|
|||||||
@@ -254,35 +254,47 @@ func ParseDeleteObjects(objs []types.ObjectIdentifier) (result []string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func FilterObjectAttributes(attrs map[types.ObjectAttributes]struct{}, output s3response.GetObjectAttributesResult) s3response.GetObjectAttributesResult {
|
func FilterObjectAttributes(attrs map[s3response.ObjectAttributes]struct{}, output s3response.GetObjectAttributesResponse) s3response.GetObjectAttributesResponse {
|
||||||
if _, ok := attrs[types.ObjectAttributesEtag]; !ok {
|
// These properties shouldn't appear in the final response body
|
||||||
|
output.LastModified = nil
|
||||||
|
output.VersionId = nil
|
||||||
|
output.DeleteMarker = nil
|
||||||
|
|
||||||
|
if _, ok := attrs[s3response.ObjectAttributesEtag]; !ok {
|
||||||
output.ETag = nil
|
output.ETag = nil
|
||||||
}
|
}
|
||||||
if _, ok := attrs[types.ObjectAttributesObjectParts]; !ok {
|
if _, ok := attrs[s3response.ObjectAttributesObjectParts]; !ok {
|
||||||
output.ObjectParts = nil
|
output.ObjectParts = nil
|
||||||
}
|
}
|
||||||
if _, ok := attrs[types.ObjectAttributesObjectSize]; !ok {
|
if _, ok := attrs[s3response.ObjectAttributesObjectSize]; !ok {
|
||||||
output.ObjectSize = nil
|
output.ObjectSize = nil
|
||||||
}
|
}
|
||||||
if _, ok := attrs[types.ObjectAttributesStorageClass]; !ok {
|
if _, ok := attrs[s3response.ObjectAttributesStorageClass]; !ok {
|
||||||
output.StorageClass = ""
|
output.StorageClass = ""
|
||||||
}
|
}
|
||||||
|
fmt.Printf("%+v\n", output)
|
||||||
|
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseObjectAttributes(ctx *fiber.Ctx) map[types.ObjectAttributes]struct{} {
|
func ParseObjectAttributes(ctx *fiber.Ctx) (map[s3response.ObjectAttributes]struct{}, error) {
|
||||||
attrs := map[types.ObjectAttributes]struct{}{}
|
attrs := map[s3response.ObjectAttributes]struct{}{}
|
||||||
|
var err error
|
||||||
ctx.Request().Header.VisitAll(func(key, value []byte) {
|
ctx.Request().Header.VisitAll(func(key, value []byte) {
|
||||||
if string(key) == "X-Amz-Object-Attributes" {
|
if string(key) == "X-Amz-Object-Attributes" {
|
||||||
oattrs := strings.Split(string(value), ",")
|
oattrs := strings.Split(string(value), ",")
|
||||||
for _, a := range oattrs {
|
for _, a := range oattrs {
|
||||||
attrs[types.ObjectAttributes(a)] = struct{}{}
|
attr := s3response.ObjectAttributes(a)
|
||||||
|
if !attr.IsValid() {
|
||||||
|
err = s3err.GetAPIError(s3err.ErrInvalidObjectAttributes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
attrs[attr] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return attrs
|
return attrs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type objLockCfg struct {
|
type objLockCfg struct {
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
|
"github.com/versity/versitygw/backend"
|
||||||
"github.com/versity/versitygw/s3response"
|
"github.com/versity/versitygw/s3response"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -283,47 +285,68 @@ func TestParseUint(t *testing.T) {
|
|||||||
|
|
||||||
func TestFilterObjectAttributes(t *testing.T) {
|
func TestFilterObjectAttributes(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
attrs map[types.ObjectAttributes]struct{}
|
attrs map[s3response.ObjectAttributes]struct{}
|
||||||
output s3response.GetObjectAttributesResult
|
output s3response.GetObjectAttributesResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
etag, objSize := "etag", int64(3222)
|
etag, objSize := "etag", int64(3222)
|
||||||
|
delMarker := true
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
want s3response.GetObjectAttributesResult
|
want s3response.GetObjectAttributesResponse
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "keep only ETag",
|
name: "keep only ETag",
|
||||||
args: args{
|
args: args{
|
||||||
attrs: map[types.ObjectAttributes]struct{}{
|
attrs: map[s3response.ObjectAttributes]struct{}{
|
||||||
types.ObjectAttributesEtag: {},
|
s3response.ObjectAttributesEtag: {},
|
||||||
},
|
},
|
||||||
output: s3response.GetObjectAttributesResult{
|
output: s3response.GetObjectAttributesResponse{
|
||||||
ObjectSize: &objSize,
|
ObjectSize: &objSize,
|
||||||
ETag: &etag,
|
ETag: &etag,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: s3response.GetObjectAttributesResult{ETag: &etag},
|
want: s3response.GetObjectAttributesResponse{ETag: &etag},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "keep multiple props",
|
name: "keep multiple props",
|
||||||
args: args{
|
args: args{
|
||||||
attrs: map[types.ObjectAttributes]struct{}{
|
attrs: map[s3response.ObjectAttributes]struct{}{
|
||||||
types.ObjectAttributesEtag: {},
|
s3response.ObjectAttributesEtag: {},
|
||||||
types.ObjectAttributesObjectSize: {},
|
s3response.ObjectAttributesObjectSize: {},
|
||||||
types.ObjectAttributesStorageClass: {},
|
s3response.ObjectAttributesStorageClass: {},
|
||||||
},
|
},
|
||||||
output: s3response.GetObjectAttributesResult{
|
output: s3response.GetObjectAttributesResponse{
|
||||||
ObjectSize: &objSize,
|
ObjectSize: &objSize,
|
||||||
ETag: &etag,
|
ETag: &etag,
|
||||||
ObjectParts: &s3response.ObjectParts{},
|
ObjectParts: &s3response.ObjectParts{},
|
||||||
VersionId: &etag,
|
VersionId: &etag,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: s3response.GetObjectAttributesResult{
|
want: s3response.GetObjectAttributesResponse{
|
||||||
ETag: &etag,
|
ETag: &etag,
|
||||||
ObjectSize: &objSize,
|
ObjectSize: &objSize,
|
||||||
VersionId: &etag,
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "make sure LastModified, DeleteMarker and VersionId are removed",
|
||||||
|
args: args{
|
||||||
|
attrs: map[s3response.ObjectAttributes]struct{}{
|
||||||
|
s3response.ObjectAttributesEtag: {},
|
||||||
|
},
|
||||||
|
output: s3response.GetObjectAttributesResponse{
|
||||||
|
ObjectSize: &objSize,
|
||||||
|
ETag: &etag,
|
||||||
|
ObjectParts: &s3response.ObjectParts{},
|
||||||
|
VersionId: &etag,
|
||||||
|
LastModified: backend.GetTimePtr(time.Now()),
|
||||||
|
DeleteMarker: &delMarker,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: s3response.GetObjectAttributesResponse{
|
||||||
|
ETag: &etag,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ const (
|
|||||||
ErrInvalidMaxUploads
|
ErrInvalidMaxUploads
|
||||||
ErrInvalidMaxParts
|
ErrInvalidMaxParts
|
||||||
ErrInvalidPartNumberMarker
|
ErrInvalidPartNumberMarker
|
||||||
|
ErrInvalidObjectAttributes
|
||||||
ErrInvalidPart
|
ErrInvalidPart
|
||||||
ErrInvalidPartNumber
|
ErrInvalidPartNumber
|
||||||
ErrInternalError
|
ErrInternalError
|
||||||
@@ -219,6 +220,11 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
|||||||
Description: "Argument partNumberMarker must be an integer.",
|
Description: "Argument partNumberMarker must be an integer.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrInvalidObjectAttributes: {
|
||||||
|
Code: "InvalidArgument",
|
||||||
|
Description: "Invalid attribute name specified.",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrNoSuchBucket: {
|
ErrNoSuchBucket: {
|
||||||
Code: "NoSuchBucket",
|
Code: "NoSuchBucket",
|
||||||
Description: "The specified bucket does not exist.",
|
Description: "The specified bucket does not exist.",
|
||||||
|
|||||||
@@ -78,30 +78,34 @@ type ListPartsResult struct {
|
|||||||
Parts []Part `xml:"Part"`
|
Parts []Part `xml:"Part"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetObjectAttributesResult struct {
|
type ObjectAttributes string
|
||||||
ETag *string
|
|
||||||
LastModified *time.Time
|
const (
|
||||||
ObjectSize *int64
|
ObjectAttributesEtag ObjectAttributes = "ETag"
|
||||||
StorageClass types.StorageClass
|
ObjectAttributesChecksum ObjectAttributes = "Checksum"
|
||||||
VersionId *string
|
ObjectAttributesObjectParts ObjectAttributes = "ObjectParts"
|
||||||
ObjectParts *ObjectParts
|
ObjectAttributesStorageClass ObjectAttributes = "StorageClass"
|
||||||
|
ObjectAttributesObjectSize ObjectAttributes = "ObjectSize"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (o ObjectAttributes) IsValid() bool {
|
||||||
|
return o == ObjectAttributesChecksum ||
|
||||||
|
o == ObjectAttributesEtag ||
|
||||||
|
o == ObjectAttributesObjectParts ||
|
||||||
|
o == ObjectAttributesObjectSize ||
|
||||||
|
o == ObjectAttributesStorageClass
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r GetObjectAttributesResult) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
type GetObjectAttributesResponse struct {
|
||||||
type Alias GetObjectAttributesResult
|
ETag *string
|
||||||
aux := &struct {
|
ObjectSize *int64
|
||||||
LastModified *string `xml:"LastModified"`
|
StorageClass types.StorageClass `xml:",omitempty"`
|
||||||
*Alias
|
ObjectParts *ObjectParts
|
||||||
}{
|
|
||||||
Alias: (*Alias)(&r),
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.LastModified != nil {
|
// Not included in the response body
|
||||||
formattedTime := r.LastModified.UTC().Format(iso8601TimeFormat)
|
VersionId *string
|
||||||
aux.LastModified = &formattedTime
|
LastModified *time.Time
|
||||||
}
|
DeleteMarker *bool
|
||||||
|
|
||||||
return e.EncodeElement(aux, start)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ObjectParts struct {
|
type ObjectParts struct {
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ func TestHeadObject(s *S3Conf) {
|
|||||||
func TestGetObjectAttributes(s *S3Conf) {
|
func TestGetObjectAttributes(s *S3Conf) {
|
||||||
GetObjectAttributes_non_existing_bucket(s)
|
GetObjectAttributes_non_existing_bucket(s)
|
||||||
GetObjectAttributes_non_existing_object(s)
|
GetObjectAttributes_non_existing_object(s)
|
||||||
|
GetObjectAttributes_invalid_attrs(s)
|
||||||
GetObjectAttributes_existing_object(s)
|
GetObjectAttributes_existing_object(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -565,6 +566,7 @@ func TestVersioning(s *S3Conf) {
|
|||||||
// HeadObject action
|
// HeadObject action
|
||||||
Versioning_HeadObject_invalid_versionId(s)
|
Versioning_HeadObject_invalid_versionId(s)
|
||||||
Versioning_HeadObject_success(s)
|
Versioning_HeadObject_success(s)
|
||||||
|
Versioning_HeadObject_without_versionId(s)
|
||||||
Versioning_HeadObject_delete_marker(s)
|
Versioning_HeadObject_delete_marker(s)
|
||||||
// GetObject action
|
// GetObject action
|
||||||
Versioning_GetObject_invalid_versionId(s)
|
Versioning_GetObject_invalid_versionId(s)
|
||||||
@@ -572,6 +574,9 @@ func TestVersioning(s *S3Conf) {
|
|||||||
Versioning_GetObject_delete_marker_without_versionId(s)
|
Versioning_GetObject_delete_marker_without_versionId(s)
|
||||||
Versioning_GetObject_delete_marker(s)
|
Versioning_GetObject_delete_marker(s)
|
||||||
Versioning_GetObject_null_versionId_obj(s)
|
Versioning_GetObject_null_versionId_obj(s)
|
||||||
|
// GetObjectAttributes action
|
||||||
|
Versioning_GetObjectAttributes_object_version(s)
|
||||||
|
Versioning_GetObjectAttributes_delete_marker(s)
|
||||||
// DeleteObject(s) actions
|
// DeleteObject(s) actions
|
||||||
Versioning_DeleteObject_delete_object_version(s)
|
Versioning_DeleteObject_delete_object_version(s)
|
||||||
Versioning_DeleteObject_non_existing_object(s)
|
Versioning_DeleteObject_non_existing_object(s)
|
||||||
@@ -720,6 +725,7 @@ func GetIntTests() IntTests {
|
|||||||
"HeadObject_success": HeadObject_success,
|
"HeadObject_success": HeadObject_success,
|
||||||
"GetObjectAttributes_non_existing_bucket": GetObjectAttributes_non_existing_bucket,
|
"GetObjectAttributes_non_existing_bucket": GetObjectAttributes_non_existing_bucket,
|
||||||
"GetObjectAttributes_non_existing_object": GetObjectAttributes_non_existing_object,
|
"GetObjectAttributes_non_existing_object": GetObjectAttributes_non_existing_object,
|
||||||
|
"GetObjectAttributes_invalid_attrs": GetObjectAttributes_invalid_attrs,
|
||||||
"GetObjectAttributes_existing_object": GetObjectAttributes_existing_object,
|
"GetObjectAttributes_existing_object": GetObjectAttributes_existing_object,
|
||||||
"GetObject_non_existing_key": GetObject_non_existing_key,
|
"GetObject_non_existing_key": GetObject_non_existing_key,
|
||||||
"GetObject_directory_object_noslash": GetObject_directory_object_noslash,
|
"GetObject_directory_object_noslash": GetObject_directory_object_noslash,
|
||||||
@@ -960,12 +966,15 @@ func GetIntTests() IntTests {
|
|||||||
"Versioning_CopyObject_special_chars": Versioning_CopyObject_special_chars,
|
"Versioning_CopyObject_special_chars": Versioning_CopyObject_special_chars,
|
||||||
"Versioning_HeadObject_invalid_versionId": Versioning_HeadObject_invalid_versionId,
|
"Versioning_HeadObject_invalid_versionId": Versioning_HeadObject_invalid_versionId,
|
||||||
"Versioning_HeadObject_success": Versioning_HeadObject_success,
|
"Versioning_HeadObject_success": Versioning_HeadObject_success,
|
||||||
|
"Versioning_HeadObject_without_versionId": Versioning_HeadObject_without_versionId,
|
||||||
"Versioning_HeadObject_delete_marker": Versioning_HeadObject_delete_marker,
|
"Versioning_HeadObject_delete_marker": Versioning_HeadObject_delete_marker,
|
||||||
"Versioning_GetObject_invalid_versionId": Versioning_GetObject_invalid_versionId,
|
"Versioning_GetObject_invalid_versionId": Versioning_GetObject_invalid_versionId,
|
||||||
"Versioning_GetObject_success": Versioning_GetObject_success,
|
"Versioning_GetObject_success": Versioning_GetObject_success,
|
||||||
"Versioning_GetObject_delete_marker_without_versionId": Versioning_GetObject_delete_marker_without_versionId,
|
"Versioning_GetObject_delete_marker_without_versionId": Versioning_GetObject_delete_marker_without_versionId,
|
||||||
"Versioning_GetObject_delete_marker": Versioning_GetObject_delete_marker,
|
"Versioning_GetObject_delete_marker": Versioning_GetObject_delete_marker,
|
||||||
"Versioning_GetObject_null_versionId_obj": Versioning_GetObject_null_versionId_obj,
|
"Versioning_GetObject_null_versionId_obj": Versioning_GetObject_null_versionId_obj,
|
||||||
|
"Versioning_GetObjectAttributes_object_version": Versioning_GetObjectAttributes_object_version,
|
||||||
|
"Versioning_GetObjectAttributes_delete_marker": Versioning_GetObjectAttributes_delete_marker,
|
||||||
"Versioning_DeleteObject_delete_object_version": Versioning_DeleteObject_delete_object_version,
|
"Versioning_DeleteObject_delete_object_version": Versioning_DeleteObject_delete_object_version,
|
||||||
"Versioning_DeleteObject_non_existing_object": Versioning_DeleteObject_non_existing_object,
|
"Versioning_DeleteObject_non_existing_object": Versioning_DeleteObject_non_existing_object,
|
||||||
"Versioning_DeleteObject_delete_a_delete_marker": Versioning_DeleteObject_delete_a_delete_marker,
|
"Versioning_DeleteObject_delete_a_delete_marker": Versioning_DeleteObject_delete_a_delete_marker,
|
||||||
|
|||||||
@@ -3375,9 +3375,11 @@ func GetObjectAttributes_non_existing_object(s *S3Conf) error {
|
|||||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||||
_, err := s3client.GetObjectAttributes(ctx, &s3.GetObjectAttributesInput{
|
_, err := s3client.GetObjectAttributes(ctx, &s3.GetObjectAttributesInput{
|
||||||
Bucket: &bucket,
|
Bucket: &bucket,
|
||||||
Key: getPtr("my-obj"),
|
Key: getPtr("my-obj"),
|
||||||
ObjectAttributes: []types.ObjectAttributes{},
|
ObjectAttributes: []types.ObjectAttributes{
|
||||||
|
types.ObjectAttributesEtag,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
cancel()
|
cancel()
|
||||||
if err := checkSdkApiErr(err, "NoSuchKey"); err != nil {
|
if err := checkSdkApiErr(err, "NoSuchKey"); err != nil {
|
||||||
@@ -3388,6 +3390,33 @@ func GetObjectAttributes_non_existing_object(s *S3Conf) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetObjectAttributes_invalid_attrs(s *S3Conf) error {
|
||||||
|
testName := "GetObjectAttributes_invalid_attrs"
|
||||||
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
|
obj := "my-obj"
|
||||||
|
_, err := putObjects(s3client, []string{obj}, bucket)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||||
|
_, err = s3client.GetObjectAttributes(ctx, &s3.GetObjectAttributesInput{
|
||||||
|
Bucket: &bucket,
|
||||||
|
Key: &obj,
|
||||||
|
ObjectAttributes: []types.ObjectAttributes{
|
||||||
|
types.ObjectAttributesEtag,
|
||||||
|
types.ObjectAttributes("Invalid_argument"),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
cancel()
|
||||||
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidObjectAttributes)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func GetObjectAttributes_existing_object(s *S3Conf) error {
|
func GetObjectAttributes_existing_object(s *S3Conf) error {
|
||||||
testName := "GetObjectAttributes_existing_object"
|
testName := "GetObjectAttributes_existing_object"
|
||||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
@@ -3438,11 +3467,14 @@ func GetObjectAttributes_existing_object(s *S3Conf) error {
|
|||||||
return fmt.Errorf("expected object size to be %v, instead got %v", data_len, *out.ObjectSize)
|
return fmt.Errorf("expected object size to be %v, instead got %v", data_len, *out.ObjectSize)
|
||||||
}
|
}
|
||||||
if out.Checksum != nil {
|
if out.Checksum != nil {
|
||||||
return fmt.Errorf("expected checksum do be nil, instead got %v", *out.Checksum)
|
return fmt.Errorf("expected checksum to be nil, instead got %v", *out.Checksum)
|
||||||
}
|
}
|
||||||
if out.StorageClass != types.StorageClassStandard {
|
if out.StorageClass != types.StorageClassStandard {
|
||||||
return fmt.Errorf("expected the storage class to be %v, instead got %v", types.StorageClassStandard, out.StorageClass)
|
return fmt.Errorf("expected the storage class to be %v, instead got %v", types.StorageClassStandard, out.StorageClass)
|
||||||
}
|
}
|
||||||
|
if out.LastModified == nil {
|
||||||
|
return fmt.Errorf("expected non nil LastModified")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -10848,19 +10880,6 @@ func PutObject_name_too_long(s *S3Conf) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Versioning tests
|
|
||||||
func PutBucketVersioning_non_existing_bucket(s *S3Conf) error {
|
|
||||||
testName := "PutBucketVersioning_non_existing_bucket"
|
|
||||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
||||||
err := putBucketVersioningStatus(s3client, getBucketName(), types.BucketVersioningStatusSuspended)
|
|
||||||
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func HeadObject_name_too_long(s *S3Conf) error {
|
func HeadObject_name_too_long(s *S3Conf) error {
|
||||||
testName := "HeadObject_name_too_long"
|
testName := "HeadObject_name_too_long"
|
||||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
@@ -10878,6 +10897,35 @@ func HeadObject_name_too_long(s *S3Conf) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DeleteObject_name_too_long(s *S3Conf) error {
|
||||||
|
testName := "DeleteObject_name_too_long"
|
||||||
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||||
|
_, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
||||||
|
Bucket: &bucket,
|
||||||
|
Key: getPtr(genRandString(300)),
|
||||||
|
})
|
||||||
|
cancel()
|
||||||
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrKeyTooLong)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Versioning tests
|
||||||
|
func PutBucketVersioning_non_existing_bucket(s *S3Conf) error {
|
||||||
|
testName := "PutBucketVersioning_non_existing_bucket"
|
||||||
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
|
err := putBucketVersioningStatus(s3client, getBucketName(), types.BucketVersioningStatusSuspended)
|
||||||
|
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func PutBucketVersioning_invalid_status(s *S3Conf) error {
|
func PutBucketVersioning_invalid_status(s *S3Conf) error {
|
||||||
testName := "PutBucketVersioning_invalid_status"
|
testName := "PutBucketVersioning_invalid_status"
|
||||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
@@ -11405,22 +11453,6 @@ func Versioning_HeadObject_invalid_versionId(s *S3Conf) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteObject_name_too_long(s *S3Conf) error {
|
|
||||||
testName := "DeleteObject_name_too_long"
|
|
||||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
|
||||||
_, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
|
||||||
Bucket: &bucket,
|
|
||||||
Key: getPtr(genRandString(300)),
|
|
||||||
})
|
|
||||||
cancel()
|
|
||||||
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrKeyTooLong)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func Versioning_HeadObject_success(s *S3Conf) error {
|
func Versioning_HeadObject_success(s *S3Conf) error {
|
||||||
testName := "Versioning_HeadObject_success"
|
testName := "Versioning_HeadObject_success"
|
||||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
@@ -11456,6 +11488,35 @@ func Versioning_HeadObject_success(s *S3Conf) error {
|
|||||||
}, withVersioning(types.BucketVersioningStatusEnabled))
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Versioning_HeadObject_without_versionId(s *S3Conf) error {
|
||||||
|
testName := "Versioning_HeadObject_without_versionId"
|
||||||
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
|
obj := "my-obj"
|
||||||
|
versions, err := createObjVersions(s3client, bucket, obj, 3)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
lastVersion := versions[0]
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||||
|
res, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{
|
||||||
|
Bucket: &bucket,
|
||||||
|
Key: &obj,
|
||||||
|
})
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if getString(res.VersionId) != *lastVersion.VersionId {
|
||||||
|
return fmt.Errorf("expected versionId to be %v, instead got %v", *lastVersion.VersionId, getString(res.VersionId))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
||||||
|
}
|
||||||
|
|
||||||
func Versioning_HeadObject_delete_marker(s *S3Conf) error {
|
func Versioning_HeadObject_delete_marker(s *S3Conf) error {
|
||||||
testName := "Versioning_HeadObject_delete_marker"
|
testName := "Versioning_HeadObject_delete_marker"
|
||||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
@@ -11727,6 +11788,97 @@ func Versioning_GetObject_null_versionId_obj(s *S3Conf) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Versioning_GetObjectAttributes_object_version(s *S3Conf) error {
|
||||||
|
testName := "Versioning_GetObjectAttributes_object_version"
|
||||||
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
|
obj := "my-obj"
|
||||||
|
versions, err := createObjVersions(s3client, bucket, obj, 1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
version := versions[0]
|
||||||
|
|
||||||
|
getObjAttrs := func(versionId *string) (*s3.GetObjectAttributesOutput, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||||
|
res, err := s3client.GetObjectAttributes(ctx, &s3.GetObjectAttributesInput{
|
||||||
|
Bucket: &bucket,
|
||||||
|
Key: &obj,
|
||||||
|
VersionId: versionId,
|
||||||
|
ObjectAttributes: []types.ObjectAttributes{
|
||||||
|
types.ObjectAttributesEtag,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
cancel()
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// By specifying the versionId
|
||||||
|
res, err := getObjAttrs(version.VersionId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if getString(res.ETag) != *version.ETag {
|
||||||
|
return fmt.Errorf("expected the uploaded object ETag to be %v, instead got %v", *version.ETag, getString(res.ETag))
|
||||||
|
}
|
||||||
|
if getString(res.VersionId) != *version.VersionId {
|
||||||
|
return fmt.Errorf("expected the uploaded versionId to be %v, instead got %v", *version.VersionId, getString(res.VersionId))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Without versionId
|
||||||
|
res, err = getObjAttrs(nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if getString(res.ETag) != *version.ETag {
|
||||||
|
return fmt.Errorf("expected the uploaded object ETag to be %v, instead got %v", *version.ETag, getString(res.ETag))
|
||||||
|
}
|
||||||
|
if getString(res.VersionId) != *version.VersionId {
|
||||||
|
return fmt.Errorf("expected the uploaded object versionId to be %v, instead got %v", *version.VersionId, getString(res.VersionId))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Versioning_GetObjectAttributes_delete_marker(s *S3Conf) error {
|
||||||
|
testName := "Versioning_GetObjectAttributes_delete_marker"
|
||||||
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
|
obj := "my-obj"
|
||||||
|
_, err := createObjVersions(s3client, bucket, obj, 1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||||
|
res, err := s3client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
||||||
|
Bucket: &bucket,
|
||||||
|
Key: &obj,
|
||||||
|
})
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||||
|
_, err = s3client.GetObjectAttributes(ctx, &s3.GetObjectAttributesInput{
|
||||||
|
Bucket: &bucket,
|
||||||
|
Key: &obj,
|
||||||
|
VersionId: res.VersionId,
|
||||||
|
ObjectAttributes: []types.ObjectAttributes{
|
||||||
|
types.ObjectAttributesEtag,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
cancel()
|
||||||
|
if err := checkSdkApiErr(err, "NoSuchKey"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}, withVersioning(types.BucketVersioningStatusEnabled))
|
||||||
|
}
|
||||||
|
|
||||||
func Versioning_DeleteObject_delete_object_version(s *S3Conf) error {
|
func Versioning_DeleteObject_delete_object_version(s *S3Conf) error {
|
||||||
testName := "Versioning_DeleteObject_delete_object_version"
|
testName := "Versioning_DeleteObject_delete_object_version"
|
||||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user