Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NEW API: GetObjectAttributes #1921

Merged
merged 14 commits into from
Jan 8, 2024
201 changes: 201 additions & 0 deletions api-get-object-attributes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
/*
* MinIO Go Library for Amazon S3 Compatible Cloud Storage
* Copyright 2020 MinIO, Inc.
* 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 minio
zveinn marked this conversation as resolved.
Show resolved Hide resolved

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 are options used for the GetObjectAttributes API
//
// - MaxParts
// How many parts the caller wants to be returned (default: 1000)
//
// - VersionID
// The object version you want to attributes for
//
// - PartNumberMarker
// the listing will start AFTER the part matching PartNumberMarker
//
// - ServerSideEncryption
// The server-side encryption algorithm used when storing this object in Minio
type ObjectAttributesOptions struct {
MaxParts int
VersionID string
PartNumberMarker int
ServerSideEncryption encrypt.ServerSide
}

// ObjectAttributes is the response object returned by the GetObjectAttributes API
//
// - VersionID
// The object version
//
// - LastModified
// The last time the object was modified
//
// - ObjectAttributesResponse
// Contains more information about the object
type ObjectAttributes struct {
VersionID string
LastModified time.Time
ObjectAttributesResponse
}

// ObjectAttributesResponse contains details returned by the GetObjectAttributes API
//
// Noteworthy fields:
//
// - ObjectParts.PartsCount
// Contains the total part count for the object (not the current response)
//
// - ObjectParts.PartNumberMarker
// Pagination of parts will begin at (but not include) PartNumberMarker
//
// - ObjectParts.NextPartNumberMarket
// The next PartNumberMarker to be used in order to continue pagination
//
// - ObjectParts.IsTruncated
// Indicates if the last part is included in the request (does not check if parts are missing from the start of the list, ONLY the end)
//
// - ObjectParts.MaxParts
// Reflects the MaxParts used by the caller or the default MaxParts value of the API
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
zveinn marked this conversation as resolved.
Show resolved Hide resolved
Parts []*ObjectAttributePart `xml:"Part"`
}
}

// ObjectAttributePart is used by ObjectAttributesResponse to describe an object part
type ObjectAttributePart struct {
ChecksumCRC32 string `xml:",omitempty"`
ChecksumCRC32C string `xml:",omitempty"`
ChecksumSHA1 string `xml:",omitempty"`
ChecksumSHA256 string `xml:",omitempty"`
PartNumber int
Size int
}

func (o *ObjectAttributes) parseResponse(resp *http.Response) (err error) {
mod, err := parseRFC7231Time(resp.Header.Get("Last-Modified"))
if err != nil {
return err
}
o.LastModified = mod
o.VersionID = resp.Header.Get(amzVersionID)

response := new(ObjectAttributesResponse)
if err := xml.NewDecoder(resp.Body).Decode(response); err != nil {
return err
}
o.ObjectAttributesResponse = *response

return
}

// GetObjectAttributes API combines HeadObject and ListParts.
// More details on usage can be found in the documentation for ObjectAttributesOptions{}
func (c *Client) GetObjectAttributes(ctx context.Context, bucketName, objectName string, opts ObjectAttributesOptions) (*ObjectAttributes, error) {
if err := s3utils.CheckValidBucketName(bucketName); err != nil {
return nil, err
}

if err := s3utils.CheckValidObjectName(objectName); err != nil {
return nil, err
}

urlValues := make(url.Values)
urlValues.Add("attributes", "")
if opts.VersionID != "" {
urlValues.Add("versionId", opts.VersionID)
}

headers := make(http.Header)
headers.Set(amzObjectAttributes, GetObjectAttributesTags)

if opts.PartNumberMarker > 0 {
headers.Set(amzPartNumberMarker, strconv.Itoa(opts.PartNumberMarker))
}

if opts.MaxParts > 0 {
headers.Set(amzMaxParts, strconv.Itoa(opts.MaxParts))
} else {
headers.Set(amzMaxParts, strconv.Itoa(GetObjectAttributesMaxParts))
}

if opts.ServerSideEncryption != nil {
opts.ServerSideEncryption.Marshal(headers)
}

resp, err := c.executeMethod(ctx, http.MethodGet, requestMetadata{
bucketName: bucketName,
objectName: objectName,
queryValues: urlValues,
contentSHA256Hex: emptySHA256Hex,
customHeader: headers,
})
if err != nil {
return nil, err
}

defer closeResponse(resp)

hasEtag := resp.Header.Get(ETag)
if hasEtag != "" {
return nil, errors.New("getObjectAttributes is not supported by the current endpoint version")
}

if resp.StatusCode != http.StatusOK {
ER := new(ErrorResponse)
if err := xml.NewDecoder(resp.Body).Decode(ER); err != nil {
return nil, err
}

return nil, *ER
}

OA := new(ObjectAttributes)
err = OA.parseResponse(resp)
if err != nil {
return nil, err
}

return OA, nil
}
20 changes: 20 additions & 0 deletions constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,32 @@ const (
)

const (
// GetObjectAttributesTags are tags used to defined
// return values for the GetObjectAttributes API
GetObjectAttributesTags = "ETag,Checksum,StorageClass,ObjectSize,ObjectParts"
// GetObjectAttributesMaxParts defined the default maximum
// number of parts returned by GetObjectAttributes
GetObjectAttributesMaxParts = 1000
)

const (
// Response Headers

// ETag is a common response header
ETag = "ETag"

// Storage class header.
amzStorageClass = "X-Amz-Storage-Class"

// Website redirect location header
amzWebsiteRedirectLocation = "X-Amz-Website-Redirect-Location"

// GetObjectAttributes headers
amzPartNumberMarker = "X-Amz-Part-Number-Marker"
amzExpectedBucketOnwer = "X-Amz-Expected-Bucket-Owner"
amzMaxParts = "X-Amz-Max-Parts"
amzObjectAttributes = "X-Amz-Object-Attributes"

// Object Tagging headers
amzTaggingHeader = "X-Amz-Tagging"
amzTaggingHeaderDirective = "X-Amz-Tagging-Directive"
Expand Down
56 changes: 55 additions & 1 deletion docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ func main() {
| | [`GetObjectTagging`](#GetObjectTagging) | | | |
| | [`RemoveObjectTagging`](#RemoveObjectTagging) | | | |
| | [`RestoreObject`](#RestoreObject) | | | |
| | [`GetObjectAttributes`](#GetObjectAttributes) | | | |

## 1. Constructor
<a name="MinIO"></a>
Expand Down Expand Up @@ -445,8 +446,8 @@ __minio.GetObjectOptions__
|:---|:---|:---|
| `opts.ServerSideEncryption` | _encrypt.ServerSide_ | Interface provided by `encrypt` package to specify server-side-encryption. (For more information see https://godoc.org/github.com/minio/minio-go/v7) |
| `opts.Internal` | _minio.AdvancedGetOptions_ | This option is intended for internal use by MinIO server. This option should not be set unless the application is aware of intended use.
__Return Value__

__Return Value__

|Param |Type |Description |
|:---|:---| :---|
Expand Down Expand Up @@ -1194,6 +1195,59 @@ if err != nil {
}
```

<a name="GetObjectAttributes"></a>
### GetObjectAttributes(ctx context.Context, bucketName, objectName string, opts ObjectAttributesOptions) (*ObjectAttributes, error)
Returns a stream of the object data. Most of the common errors occur when reading the stream.


__Parameters__


|Param |Type |Description |
|:---|:---| :---|
|`ctx` | _context.Context_ | Custom context for timeout/cancellation of the call|
|`bucketName` | _string_ |Name of the bucket |
|`objectName` | _string_ |Name of the object |
|`opts` | _minio.ObjectAttributesOptions_ | Configuration for pagination and selection of object attributes |


__minio.ObjectAttributesOptions__

|Field | Type | Description |
|:---|:---|:---|
| `opts.ServerSideEncryption` | _encrypt.ServerSide_ | Interface provided by `encrypt` package to specify server-side-encryption. (For more information see https://godoc.org/github.com/minio/minio-go/v7) |
| `opts.MaxParts` | _int | This option defines how many parts should be returned by the API
| `opts.VersionID` | _string | VersionID defines which version of the object will be used
| `opts.PartNumberMarker` | _int | This options defines which part number pagination will start after, the part which number is equal to PartNumberMarker will not be included in the response

__Return Value__

|Param |Type |Description |
|:---|:---| :---|
|`objectAttributes` | _*minio.ObjectAttributes_ |_minio.ObjectAttributes_ contains the information about the object and it's parts. |

__Example__


```go
objectAttributes, err := c.GetObjectAttributes(
context.Background(),
"your-bucket",
"your-object",
minio.ObjectAttributesOptions{
VersionID:"object-version-id",
NextPartMarker:0,
MaxParts:100,
})

if err != nil {
fmt.Println(err)
return
}

fmt.Println(objectAttributes)
```


<a name="RemoveIncompleteUpload"></a>
### RemoveIncompleteUpload(ctx context.Context, bucketName, objectName string) error
Expand Down