mirror of
https://github.com/minio/minio.git
synced 2025-01-11 15:03:22 -05:00
preserve Version and DeleteMarker sort order in the list XML response (#15819)
This commit is contained in:
parent
e856e10ac2
commit
dfe0c96b87
@ -34,6 +34,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/hash"
|
"github.com/minio/minio/internal/hash"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
|
xxml "github.com/minio/xxml"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -90,8 +91,7 @@ type ListVersionsResponse struct {
|
|||||||
IsTruncated bool
|
IsTruncated bool
|
||||||
|
|
||||||
CommonPrefixes []CommonPrefix
|
CommonPrefixes []CommonPrefix
|
||||||
DeleteMarkers []DeleteMarkerVersion `xml:"DeleteMarker,omitempty"`
|
Versions []ObjectVersion
|
||||||
Versions []ObjectVersion `xml:"Version,omitempty"`
|
|
||||||
|
|
||||||
// Encoding type used to encode object keys in the response.
|
// Encoding type used to encode object keys in the response.
|
||||||
EncodingType string `xml:"EncodingType,omitempty"`
|
EncodingType string `xml:"EncodingType,omitempty"`
|
||||||
@ -256,6 +256,19 @@ type ObjectVersion struct {
|
|||||||
Object
|
Object
|
||||||
IsLatest bool
|
IsLatest bool
|
||||||
VersionID string `xml:"VersionId"`
|
VersionID string `xml:"VersionId"`
|
||||||
|
|
||||||
|
isDeleteMarker bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalXML - marshal ObjectVersion
|
||||||
|
func (o ObjectVersion) MarshalXML(e *xxml.Encoder, start xxml.StartElement) error {
|
||||||
|
if o.isDeleteMarker {
|
||||||
|
start.Name.Local = "DeleteMarker"
|
||||||
|
} else {
|
||||||
|
start.Name.Local = "Version"
|
||||||
|
}
|
||||||
|
type objectVersionWrapper ObjectVersion
|
||||||
|
return e.EncodeElement(objectVersionWrapper(o), start)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteMarkerVersion container for delete marker metadata
|
// DeleteMarkerVersion container for delete marker metadata
|
||||||
@ -482,7 +495,6 @@ func generateListBucketsResponse(buckets []BucketInfo) ListBucketsResponse {
|
|||||||
// generates an ListBucketVersions response for the said bucket with other enumerated options.
|
// generates an ListBucketVersions response for the said bucket with other enumerated options.
|
||||||
func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType string, maxKeys int, resp ListObjectVersionsInfo) ListVersionsResponse {
|
func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType string, maxKeys int, resp ListObjectVersionsInfo) ListVersionsResponse {
|
||||||
versions := make([]ObjectVersion, 0, len(resp.Objects))
|
versions := make([]ObjectVersion, 0, len(resp.Objects))
|
||||||
deleteMarkers := make([]DeleteMarkerVersion, 0, len(resp.Objects))
|
|
||||||
|
|
||||||
owner := Owner{
|
owner := Owner{
|
||||||
ID: globalMinioDefaultOwnerID,
|
ID: globalMinioDefaultOwnerID,
|
||||||
@ -494,21 +506,6 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
|
|||||||
if object.Name == "" {
|
if object.Name == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if object.DeleteMarker {
|
|
||||||
deleteMarker := DeleteMarkerVersion{
|
|
||||||
Key: s3EncodeName(object.Name, encodingType),
|
|
||||||
LastModified: object.ModTime.UTC().Format(iso8601TimeFormat),
|
|
||||||
Owner: owner,
|
|
||||||
VersionID: object.VersionID,
|
|
||||||
}
|
|
||||||
if deleteMarker.VersionID == "" {
|
|
||||||
deleteMarker.VersionID = nullVersionID
|
|
||||||
}
|
|
||||||
deleteMarker.IsLatest = object.IsLatest
|
|
||||||
deleteMarkers = append(deleteMarkers, deleteMarker)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
content := ObjectVersion{}
|
content := ObjectVersion{}
|
||||||
content.Key = s3EncodeName(object.Name, encodingType)
|
content.Key = s3EncodeName(object.Name, encodingType)
|
||||||
content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat)
|
content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat)
|
||||||
@ -527,12 +524,12 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
|
|||||||
content.VersionID = nullVersionID
|
content.VersionID = nullVersionID
|
||||||
}
|
}
|
||||||
content.IsLatest = object.IsLatest
|
content.IsLatest = object.IsLatest
|
||||||
|
content.isDeleteMarker = object.DeleteMarker
|
||||||
versions = append(versions, content)
|
versions = append(versions, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
data.Name = bucket
|
data.Name = bucket
|
||||||
data.Versions = versions
|
data.Versions = versions
|
||||||
data.DeleteMarkers = deleteMarkers
|
|
||||||
data.EncodingType = encodingType
|
data.EncodingType = encodingType
|
||||||
data.Prefix = s3EncodeName(prefix, encodingType)
|
data.Prefix = s3EncodeName(prefix, encodingType)
|
||||||
data.KeyMarker = s3EncodeName(marker, encodingType)
|
data.KeyMarker = s3EncodeName(marker, encodingType)
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -102,6 +103,7 @@ func runAllTests(suite *TestSuiteCommon, c *check) {
|
|||||||
suite.TestContentTypePersists(c)
|
suite.TestContentTypePersists(c)
|
||||||
suite.TestPartialContent(c)
|
suite.TestPartialContent(c)
|
||||||
suite.TestListObjectsHandler(c)
|
suite.TestListObjectsHandler(c)
|
||||||
|
suite.TestListObjectVersionsOutputOrderHandler(c)
|
||||||
suite.TestListObjectsHandlerErrors(c)
|
suite.TestListObjectsHandlerErrors(c)
|
||||||
suite.TestPutBucketErrors(c)
|
suite.TestPutBucketErrors(c)
|
||||||
suite.TestGetObjectLarge10MiB(c)
|
suite.TestGetObjectLarge10MiB(c)
|
||||||
@ -1636,6 +1638,70 @@ func (s *TestSuiteCommon) TestListObjectsHandler(c *check) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestListObjectVersionsHandler - checks the order of <Version>
|
||||||
|
// and <DeleteMarker> XML tags in a version listing
|
||||||
|
func (s *TestSuiteCommon) TestListObjectVersionsOutputOrderHandler(c *check) {
|
||||||
|
// generate a random bucket name.
|
||||||
|
bucketName := getRandomBucketName()
|
||||||
|
// HTTP request to create the bucket.
|
||||||
|
makeBucketRequest, err := newTestSignedRequest(http.MethodPut, getMakeBucketURL(s.endPoint, bucketName),
|
||||||
|
0, nil, s.accessKey, s.secretKey, s.signer)
|
||||||
|
c.Assert(err, nil)
|
||||||
|
// execute the HTTP request to create bucket.
|
||||||
|
response, err := s.client.Do(makeBucketRequest)
|
||||||
|
c.Assert(err, nil)
|
||||||
|
c.Assert(response.StatusCode, http.StatusOK)
|
||||||
|
|
||||||
|
// HTTP request to create the bucket.
|
||||||
|
enableVersioningBody := []byte("<VersioningConfiguration><Status>Enabled</Status></VersioningConfiguration>")
|
||||||
|
enableVersioningBucketRequest, err := newTestSignedRequest(http.MethodPut, getBucketVersioningConfigURL(s.endPoint, bucketName),
|
||||||
|
int64(len(enableVersioningBody)), bytes.NewReader(enableVersioningBody), s.accessKey, s.secretKey, s.signer)
|
||||||
|
c.Assert(err, nil)
|
||||||
|
// execute the HTTP request to create bucket.
|
||||||
|
response, err = s.client.Do(enableVersioningBucketRequest)
|
||||||
|
c.Assert(err, nil)
|
||||||
|
c.Assert(response.StatusCode, http.StatusOK)
|
||||||
|
|
||||||
|
for _, objectName := range []string{"file.1", "file.2"} {
|
||||||
|
buffer := bytes.NewReader([]byte("testcontent"))
|
||||||
|
putRequest, err := newTestSignedRequest(http.MethodPut, getPutObjectURL(s.endPoint, bucketName, objectName),
|
||||||
|
int64(buffer.Len()), buffer, s.accessKey, s.secretKey, s.signer)
|
||||||
|
c.Assert(err, nil)
|
||||||
|
response, err = s.client.Do(putRequest)
|
||||||
|
c.Assert(err, nil)
|
||||||
|
c.Assert(response.StatusCode, http.StatusOK)
|
||||||
|
|
||||||
|
delRequest, err := newTestSignedRequest(http.MethodDelete, getDeleteObjectURL(s.endPoint, bucketName, objectName),
|
||||||
|
0, nil, s.accessKey, s.secretKey, s.signer)
|
||||||
|
c.Assert(err, nil)
|
||||||
|
response, err = s.client.Do(delRequest)
|
||||||
|
c.Assert(err, nil)
|
||||||
|
c.Assert(response.StatusCode, http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create listObjectsV1 request with valid parameters
|
||||||
|
request, err := newTestSignedRequest(http.MethodGet, getListObjectVersionsURL(s.endPoint, bucketName, "", "1000", ""),
|
||||||
|
0, nil, s.accessKey, s.secretKey, s.signer)
|
||||||
|
c.Assert(err, nil)
|
||||||
|
// execute the HTTP request.
|
||||||
|
response, err = s.client.Do(request)
|
||||||
|
c.Assert(err, nil)
|
||||||
|
c.Assert(response.StatusCode, http.StatusOK)
|
||||||
|
|
||||||
|
getContent, err := io.ReadAll(response.Body)
|
||||||
|
c.Assert(err, nil)
|
||||||
|
|
||||||
|
r := regexp.MustCompile(
|
||||||
|
`<ListVersionsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">.*` +
|
||||||
|
`<DeleteMarker><Key>file.1</Key>.*<IsLatest>true</IsLatest>.*</DeleteMarker>` +
|
||||||
|
`<Version><Key>file.1</Key>.*<IsLatest>false</IsLatest>.*</Version>` +
|
||||||
|
`<DeleteMarker><Key>file.2</Key>.*<IsLatest>true</IsLatest>.*</DeleteMarker>` +
|
||||||
|
`<Version><Key>file.2</Key>.*<IsLatest>false</IsLatest>.*</Version>` +
|
||||||
|
`</ListVersionsResult>`)
|
||||||
|
|
||||||
|
c.Assert(r.MatchString(string(getContent)), true)
|
||||||
|
}
|
||||||
|
|
||||||
// TestListObjectsSpecialCharactersHandler - Setting valid parameters to List Objects
|
// TestListObjectsSpecialCharactersHandler - Setting valid parameters to List Objects
|
||||||
// and then asserting the response with the expected one.
|
// and then asserting the response with the expected one.
|
||||||
func (s *TestSuiteCommon) TestListObjectsSpecialCharactersHandler(c *check) {
|
func (s *TestSuiteCommon) TestListObjectsSpecialCharactersHandler(c *check) {
|
||||||
|
@ -1321,6 +1321,13 @@ func getMakeBucketURL(endPoint, bucketName string) string {
|
|||||||
return makeTestTargetURL(endPoint, bucketName, "", url.Values{})
|
return makeTestTargetURL(endPoint, bucketName, "", url.Values{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return URL for creating the bucket.
|
||||||
|
func getBucketVersioningConfigURL(endPoint, bucketName string) string {
|
||||||
|
vals := make(url.Values)
|
||||||
|
vals.Set("versioning", "")
|
||||||
|
return makeTestTargetURL(endPoint, bucketName, "", vals)
|
||||||
|
}
|
||||||
|
|
||||||
// return URL for listing buckets.
|
// return URL for listing buckets.
|
||||||
func getListBucketURL(endPoint string) string {
|
func getListBucketURL(endPoint string) string {
|
||||||
return makeTestTargetURL(endPoint, "", "", url.Values{})
|
return makeTestTargetURL(endPoint, "", "", url.Values{})
|
||||||
@ -1369,6 +1376,19 @@ func getListObjectsV1URL(endPoint, bucketName, prefix, maxKeys, encodingType str
|
|||||||
return makeTestTargetURL(endPoint, bucketName, prefix, queryValue)
|
return makeTestTargetURL(endPoint, bucketName, prefix, queryValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return URL for listing objects in the bucket with V1 legacy API.
|
||||||
|
func getListObjectVersionsURL(endPoint, bucketName, prefix, maxKeys, encodingType string) string {
|
||||||
|
queryValue := url.Values{}
|
||||||
|
if maxKeys != "" {
|
||||||
|
queryValue.Set("max-keys", maxKeys)
|
||||||
|
}
|
||||||
|
if encodingType != "" {
|
||||||
|
queryValue.Set("encoding-type", encodingType)
|
||||||
|
}
|
||||||
|
queryValue.Set("versions", "")
|
||||||
|
return makeTestTargetURL(endPoint, bucketName, prefix, queryValue)
|
||||||
|
}
|
||||||
|
|
||||||
// return URL for listing objects in the bucket with V2 API.
|
// return URL for listing objects in the bucket with V2 API.
|
||||||
func getListObjectsV2URL(endPoint, bucketName, prefix, maxKeys, fetchOwner, encodingType string) string {
|
func getListObjectsV2URL(endPoint, bucketName, prefix, maxKeys, fetchOwner, encodingType string) string {
|
||||||
queryValue := url.Values{}
|
queryValue := url.Values{}
|
||||||
|
Loading…
Reference in New Issue
Block a user