diff --git a/api-get-object-attributes.go b/api-get-object-attributes.go index ec4c08414..7cb1e5d8a 100644 --- a/api-get-object-attributes.go +++ b/api-get-object-attributes.go @@ -3,41 +3,32 @@ package minio import ( "context" "encoding/xml" - "errors" "net/http" "net/url" - "strconv" "time" "github.com/minio/minio-go/v7/pkg/encrypt" "github.com/minio/minio-go/v7/pkg/s3utils" ) -// ObjectAttributesOptions ... -// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectAttributes.html +// ObjectAttributesOptions is an API call that combines +// HeadObject and ListParts. +// +// VersionID - The object version you want to attributes for +// ServerSideEncryption - The server-side encryption algorithm used when storing this object in Minio type ObjectAttributesOptions struct { - ObjectAttributes string VersionID string - MaxParts int - PartNumberMarker int ServerSideEncryption encrypt.ServerSide - - // Bucket onwer is an S3 specific parameter - BucketOwner string } // ObjectAttributes ... -// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectAttributes.html type ObjectAttributes struct { - ObjectAttributesResponse - LastModified time.Time - VersionID string - RequestCharged string + objectAttributesResponse + LastModified time.Time + VersionID string } -// ParseResponse ... -// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectAttributes.html -func (o *ObjectAttributes) ParseResponse(resp *http.Response) (err error) { +func (o *ObjectAttributes) parseResponse(resp *http.Response) (err error) { mod, err := parseRFC7231Time(resp.Header.Get("Last-Modified")) if err != nil { return err @@ -45,53 +36,47 @@ func (o *ObjectAttributes) ParseResponse(resp *http.Response) (err error) { o.LastModified = mod o.VersionID = resp.Header.Get(amzVersionID) - response := new(ObjectAttributesResponse) + response := new(objectAttributesResponse) if err := xml.NewDecoder(resp.Body).Decode(response); err != nil { return err } - o.ObjectAttributesResponse = *response + o.objectAttributesResponse = *response return } -// ObjectAttributesResponse ... -// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectAttributes.html -type ObjectAttributesResponse struct { - ETag string `xml:",omitempty"` - Checksum struct { +type objectAttributesResponse struct { + ETag string `xml:",omitempty"` + StorageClass string + ObjectSize int + Checksum struct { ChecksumCRC32 string `xml:",omitempty"` ChecksumCRC32C string `xml:",omitempty"` ChecksumSHA1 string `xml:",omitempty"` ChecksumSHA256 string `xml:",omitempty"` } ObjectParts struct { - PartsCount int - PartNumberMarker int - NextPartNumberMarker int - MaxParts int - IsTruncated bool - Parts []*struct { - ChecksumCRC32 string `xml:",omitempty"` - ChecksumCRC32C string `xml:",omitempty"` - ChecksumSHA1 string `xml:",omitempty"` - ChecksumSHA256 string `xml:",omitempty"` - PartNumber int - Size int - } `xml:"Part"` + PartsCount int + Parts []*objectPart `xml:"Part"` } - StorageClass string - ObjectSize int } -// GetObjectAttributes verifies if object exists, you have permission to access it -// and returns information about the object. -// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectAttributes.html -func (c *Client) GetObjectAttributes(ctx context.Context, bucketName, objectName string, opts ObjectAttributesOptions) (ObjectAttributes, error) { - // Input validation. +type objectPart struct { + ChecksumCRC32 string `xml:",omitempty"` + ChecksumCRC32C string `xml:",omitempty"` + ChecksumSHA1 string `xml:",omitempty"` + ChecksumSHA256 string `xml:",omitempty"` + PartNumber int + Size int +} +// GetObjectAttributes ... +// This API combines HeadObject and ListParts. +func (c *Client) GetObjectAttributes(ctx context.Context, bucketName, objectName string, opts ObjectAttributesOptions) (ObjectAttributes, error) { if err := s3utils.CheckValidBucketName(bucketName); err != nil { return ObjectAttributes{}, err } + if err := s3utils.CheckValidObjectName(objectName); err != nil { return ObjectAttributes{}, err } @@ -103,21 +88,10 @@ func (c *Client) GetObjectAttributes(ctx context.Context, bucketName, objectName } headers := make(http.Header) - headers.Set(amzObjectAttributes, opts.ObjectAttributes) - - if len(opts.ObjectAttributes) < 1 { - return ObjectAttributes{}, errors.New("object attribute tags are required") - } - - headers.Set(amzPartNumberMarker, strconv.Itoa(opts.PartNumberMarker)) + headers.Set(amzObjectAttributes, GetObjectAttributesTags) - if opts.BucketOwner != "" { - headers.Set(amzExpectedBucketOnwer, opts.BucketOwner) - } - - if opts.MaxParts != 0 { - headers.Set(amzMaxParts, strconv.Itoa(opts.MaxParts)) - } + headers.Set(amzPartNumberMarker, "0") + headers.Set(amzMaxParts, "0") if opts.ServerSideEncryption != nil { opts.ServerSideEncryption.Marshal(headers) @@ -146,7 +120,7 @@ func (c *Client) GetObjectAttributes(ctx context.Context, bucketName, objectName defer closeResponse(resp) OA := new(ObjectAttributes) - err = OA.ParseResponse(resp) + err = OA.parseResponse(resp) if err != nil { return ObjectAttributes{}, err } diff --git a/constants.go b/constants.go index 8b9dd7cfc..88efd6f12 100644 --- a/constants.go +++ b/constants.go @@ -60,6 +60,11 @@ const ( ) const ( + GetObjectAttributesTags = "ETag,Checksum,StorageClass,ObjectSize,ObjectParts" +) + +const ( + // Storage class header. amzStorageClass = "X-Amz-Storage-Class" diff --git a/functional_tests.go b/functional_tests.go index 21be28cf0..0b24aad06 100644 --- a/functional_tests.go +++ b/functional_tests.go @@ -2862,14 +2862,7 @@ func testGetObjectAttributes() { defer cleanupVersionedBucket(bucketNameV, c) testFiles := make(map[string]*objectAttributesNewObject) - testFiles["file-1-version-1"] = &objectAttributesNewObject{ - Object: "file1", - ObjectReaderType: "datafile-100-kB", - Bucket: bucketNameV, - ContentType: "custom/contenttype", - SendContentMd5: false, - } - testFiles["file-1-version-2"] = &objectAttributesNewObject{ + testFiles["file-1"] = &objectAttributesNewObject{ Object: "file1", ObjectReaderType: "datafile-1.03-MB", Bucket: bucketNameV, @@ -2902,57 +2895,8 @@ func testGetObjectAttributes() { testTable := make(map[string]objectAttributesTableTest) - testTable["0-to-0-marker"] = objectAttributesTableTest{ - opts: minio.ObjectAttributesOptions{ - ObjectAttributes: "ETag,Checksum,ObjectParts,StorageClass,ObjectSize", - PartNumberMarker: 0, - MaxParts: 0, - }, - test: objectAttributesTestOptions{ - TestFileName: "file2", - StorageClass: "STANDARD", - HasFullChecksum: true, - HasPartChecksums: true, - HasParts: true, - }, - } - - testTable["0-marker-to-max"] = objectAttributesTableTest{ - opts: minio.ObjectAttributesOptions{ - ObjectAttributes: "ETag,Checksum,ObjectParts,StorageClass,ObjectSize", - PartNumberMarker: 0, - MaxParts: 10000, - }, - test: objectAttributesTestOptions{ - TestFileName: "file2", - StorageClass: "STANDARD", - HasFullChecksum: true, - HasPartChecksums: true, - HasParts: true, - }, - } - - testTable["0-to-1-marker"] = objectAttributesTableTest{ - opts: minio.ObjectAttributesOptions{ - ObjectAttributes: "ETag,Checksum,ObjectParts,StorageClass,ObjectSize", - PartNumberMarker: 0, - MaxParts: 1, - }, - test: objectAttributesTestOptions{ - TestFileName: "file2", - StorageClass: "STANDARD", - HasFullChecksum: true, - HasPartChecksums: true, - HasParts: true, - }, - } - - testTable["0-to-5-marker"] = objectAttributesTableTest{ - opts: minio.ObjectAttributesOptions{ - ObjectAttributes: "ETag,Checksum,ObjectParts,StorageClass,ObjectSize", - PartNumberMarker: 0, - MaxParts: 5, - }, + testTable["none-versioned"] = objectAttributesTableTest{ + opts: minio.ObjectAttributesOptions{}, test: objectAttributesTestOptions{ TestFileName: "file2", StorageClass: "STANDARD", @@ -2962,77 +2906,10 @@ func testGetObjectAttributes() { }, } - testTable["0-to-9-marker"] = objectAttributesTableTest{ - opts: minio.ObjectAttributesOptions{ - ObjectAttributes: "ETag,Checksum,ObjectParts,StorageClass,ObjectSize", - PartNumberMarker: 0, - MaxParts: 9, - }, + testTable["versioned"] = objectAttributesTableTest{ + opts: minio.ObjectAttributesOptions{}, test: objectAttributesTestOptions{ - TestFileName: "file2", - StorageClass: "STANDARD", - HasFullChecksum: true, - HasPartChecksums: true, - HasParts: true, - }, - } - - testTable["attributes-etag-only"] = objectAttributesTableTest{ - opts: minio.ObjectAttributesOptions{ - ObjectAttributes: "ETag", - PartNumberMarker: 0, - MaxParts: 0, - }, - test: objectAttributesTestOptions{ - TestFileName: "file2", - }, - } - - testTable["attributes-checksum-only"] = objectAttributesTableTest{ - opts: minio.ObjectAttributesOptions{ - ObjectAttributes: "Checksum", - PartNumberMarker: 0, - MaxParts: 0, - }, - test: objectAttributesTestOptions{ - TestFileName: "file2", - HasFullChecksum: true, - }, - } - - testTable["attributes-objectparts-only"] = objectAttributesTableTest{ - opts: minio.ObjectAttributesOptions{ - ObjectAttributes: "ObjectParts", - PartNumberMarker: 0, - MaxParts: 0, - }, - test: objectAttributesTestOptions{ - TestFileName: "file2", - HasPartChecksums: true, - HasParts: true, - }, - } - - testTable["attributes-storageclass-only"] = objectAttributesTableTest{ - opts: minio.ObjectAttributesOptions{ - ObjectAttributes: "StorageClass", - PartNumberMarker: 0, - MaxParts: 0, - }, - test: objectAttributesTestOptions{ - TestFileName: "file2", - StorageClass: "STANDARD", - }, - } - - testTable["attributes-storageclass-etag-checksum"] = objectAttributesTableTest{ - opts: minio.ObjectAttributesOptions{ - ObjectAttributes: "StorageClass,ETag,Checksum", - PartNumberMarker: 0, - MaxParts: 0, - }, - test: objectAttributesTestOptions{ - TestFileName: "file2", + TestFileName: "file1", StorageClass: "STANDARD", HasFullChecksum: true, }, @@ -3056,7 +2933,7 @@ func testGetObjectAttributes() { logError(testName, function, args, startTime, "", "GetObjectAttributes failed", err) } - v.test.NumberOfParts = s.ObjectParts.PartsCount - s.ObjectParts.PartNumberMarker + v.test.NumberOfParts = s.ObjectParts.PartsCount v.test.ETag = tf.UploadInfo.ETag v.test.ObjectSize = int(tf.UploadInfo.Size) @@ -3130,7 +3007,6 @@ func testGetObjectAttributesSSECEncryption() { } opts := minio.ObjectAttributesOptions{ - ObjectAttributes: "ETag,Checksum,ObjectSize,ObjectParts,StorageClass", ServerSideEncryption: sse, } attr, err := c.GetObjectAttributes(context.Background(), bucketName, objectName, opts) @@ -3182,9 +3058,7 @@ func testGetObjectAttributesErrorCases() { c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) - _, err = c.GetObjectAttributes(context.Background(), "unknown-bucket", "unknown-object", minio.ObjectAttributesOptions{ - ObjectAttributes: "ETag", - }) + _, err = c.GetObjectAttributes(context.Background(), "unknown-bucket", "unknown-object", minio.ObjectAttributesOptions{}) if err == nil { logError(testName, function, args, startTime, "", "GetObjectAttributes failed", nil) return @@ -3228,9 +3102,7 @@ func testGetObjectAttributesErrorCases() { defer cleanupVersionedBucket(bucketNameV, c) fmt.Println("do") - _, err = c.GetObjectAttributes(context.Background(), bucketName, "unknown-object", minio.ObjectAttributesOptions{ - ObjectAttributes: "ETag", - }) + _, err = c.GetObjectAttributes(context.Background(), bucketName, "unknown-object", minio.ObjectAttributesOptions{}) if err == nil { logError(testName, function, args, startTime, "", "GetObjectAttributes failed", nil) return @@ -3242,38 +3114,20 @@ func testGetObjectAttributesErrorCases() { return } - _, err = c.GetObjectAttributes(context.Background(), bucketName, "", minio.ObjectAttributesOptions{ - ObjectAttributes: "ETag", - }) + _, err = c.GetObjectAttributes(context.Background(), bucketName, "", minio.ObjectAttributesOptions{}) if err == nil { logError(testName, function, args, startTime, "", "GetObjectAttributes with empty object name should have failed", nil) return } - _, err = c.GetObjectAttributes(context.Background(), "", "unknown-object", minio.ObjectAttributesOptions{ - ObjectAttributes: "ETag", - }) + _, err = c.GetObjectAttributes(context.Background(), "", "unknown-object", minio.ObjectAttributesOptions{}) if err == nil { logError(testName, function, args, startTime, "", "GetObjectAttributes with empty bucket name should have failed", nil) return } - _, err = c.GetObjectAttributes(context.Background(), bucketName, "unknown-object", minio.ObjectAttributesOptions{ - ObjectAttributes: "kljsdlf", - }) - if err == nil { - logError(testName, function, args, startTime, "", "GetObjectAttributes with empty bucket name should have failed", nil) - return - } - errorResponse = err.(minio.ErrorResponse) - if errorResponse.Code != "InvalidArgument" { - logError(testName, function, args, startTime, "", "Invalid error code, expected InvalidArgument but got "+errorResponse.Code, nil) - return - } - _, err = c.GetObjectAttributes(context.Background(), bucketNameV, "unknown-object", minio.ObjectAttributesOptions{ - ObjectAttributes: "ETag", - VersionID: uuid.NewString(), + VersionID: uuid.NewString(), }) if err == nil { logError(testName, function, args, startTime, "", "GetObjectAttributes with empty bucket name should have failed", nil) @@ -3376,16 +3230,9 @@ func validateObjectAttributeRequest(OA *minio.ObjectAttributes, opts *minio.Obje } } - if strings.Contains(opts.ObjectAttributes, "ETag") { - if OA.ETag != test.ETag { - err = fmt.Errorf("Etags do not match, got %s but expected %s", OA.ETag, test.ETag) - return - } - } else if !strings.Contains(opts.ObjectAttributes, "ETag") { - if OA.ETag != "" { - err = fmt.Errorf("Was not expecting an ETag but got %s", OA.ETag) - return - } + if OA.ETag != test.ETag { + err = fmt.Errorf("Etags do not match, got %s but expected %s", OA.ETag, test.ETag) + return } if test.HasParts { @@ -3395,28 +3242,14 @@ func validateObjectAttributeRequest(OA *minio.ObjectAttributes, opts *minio.Obje } } - if strings.Contains(opts.ObjectAttributes, "StorageClass") { - if OA.StorageClass == "" { - err = fmt.Errorf("Was expecting a StorageClass but got none") - return - } - } else if !strings.Contains(opts.ObjectAttributes, "StorageClass") { - if OA.StorageClass != "" { - err = fmt.Errorf("Was NOT expecting a StorageClass but got %s", OA.StorageClass) - return - } + if OA.StorageClass == "" { + err = fmt.Errorf("Was expecting a StorageClass but got none") + return } - if strings.Contains(opts.ObjectAttributes, "ObjectSize") { - if OA.ObjectSize != test.ObjectSize { - err = fmt.Errorf("Was expecting a ObjectSize but got none") - return - } - } else if !strings.Contains(opts.ObjectAttributes, "ObjectSize") { - if OA.ObjectSize != 0 { - err = fmt.Errorf("Was NOT expecting a ObjectSize but got %d", OA.ObjectSize) - return - } + if OA.ObjectSize != test.ObjectSize { + err = fmt.Errorf("Was expecting a ObjectSize but got none") + return } if OA.ObjectParts.PartsCount != test.NumberOfParts { @@ -3424,20 +3257,6 @@ func validateObjectAttributeRequest(OA *minio.ObjectAttributes, opts *minio.Obje return } - if OA.ObjectParts.NextPartNumberMarker == OA.ObjectParts.PartsCount { - if OA.ObjectParts.IsTruncated { - err = fmt.Errorf("Expected ObjectParts to NOT be truncated, but it was") - return - } - } - - if OA.ObjectParts.NextPartNumberMarker != OA.ObjectParts.PartsCount { - if !OA.ObjectParts.IsTruncated { - err = fmt.Errorf("Expected ObjectParts to be truncated, but it was NOT") - return - } - } - return }