forked from TrueCloudLab/frostfs-s3-gw
Denis Kirillov
5ee73fad6a
Despite the spec https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectVersions.html#API_ListObjectVersions_ResponseElements says that "When the number of responses exceeds the value of MaxKeys, NextVersionIdMarker specifies the first object version not returned that satisfies the search criteria. Use this value for the version-id-marker request parameter in a subsequent request." the actual behavior of AWS S3 is returning NextVersionIdMarker as the last returned object version Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
109 lines
3 KiB
Go
109 lines
3 KiB
Go
package layer
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
|
s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
|
)
|
|
|
|
func (n *layer) ListObjectVersions(ctx context.Context, p *ListObjectVersionsParams) (*ListObjectVersionsInfo, error) {
|
|
var (
|
|
allObjects = make([]*data.ExtendedObjectInfo, 0, p.MaxKeys)
|
|
res = &ListObjectVersionsInfo{}
|
|
)
|
|
|
|
versions, err := n.getAllObjectsVersions(ctx, p.BktInfo, p.Prefix, p.Delimiter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sortedNames := make([]string, 0, len(versions))
|
|
for k := range versions {
|
|
sortedNames = append(sortedNames, k)
|
|
}
|
|
sort.Strings(sortedNames)
|
|
|
|
for _, name := range sortedNames {
|
|
sortedVersions := versions[name]
|
|
sort.Slice(sortedVersions, func(i, j int) bool {
|
|
return sortedVersions[j].NodeVersion.Timestamp < sortedVersions[i].NodeVersion.Timestamp // sort in reverse order
|
|
})
|
|
|
|
for i, version := range sortedVersions {
|
|
version.IsLatest = i == 0
|
|
allObjects = append(allObjects, version)
|
|
}
|
|
}
|
|
|
|
if allObjects, err = filterVersionsByMarker(allObjects, p); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
res.CommonPrefixes, allObjects = triageExtendedObjects(allObjects)
|
|
|
|
if len(allObjects) > p.MaxKeys {
|
|
res.IsTruncated = true
|
|
res.NextKeyMarker = allObjects[p.MaxKeys-1].ObjectInfo.Name
|
|
res.NextVersionIDMarker = allObjects[p.MaxKeys-1].ObjectInfo.VersionID()
|
|
|
|
allObjects = allObjects[:p.MaxKeys]
|
|
res.KeyMarker = p.KeyMarker
|
|
res.VersionIDMarker = p.VersionIDMarker
|
|
}
|
|
|
|
res.Version, res.DeleteMarker = triageVersions(allObjects)
|
|
return res, nil
|
|
}
|
|
|
|
func filterVersionsByMarker(objects []*data.ExtendedObjectInfo, p *ListObjectVersionsParams) ([]*data.ExtendedObjectInfo, error) {
|
|
if p.KeyMarker == "" {
|
|
return objects, nil
|
|
}
|
|
|
|
for i, obj := range objects {
|
|
if obj.ObjectInfo.Name == p.KeyMarker {
|
|
for j := i; j < len(objects); j++ {
|
|
if objects[j].ObjectInfo.Name != obj.ObjectInfo.Name {
|
|
if p.VersionIDMarker == "" {
|
|
return objects[j:], nil
|
|
}
|
|
break
|
|
}
|
|
if objects[j].ObjectInfo.VersionID() == p.VersionIDMarker {
|
|
return objects[j+1:], nil
|
|
}
|
|
}
|
|
return nil, s3errors.GetAPIError(s3errors.ErrInvalidVersion)
|
|
} else if obj.ObjectInfo.Name > p.KeyMarker {
|
|
if p.VersionIDMarker != "" {
|
|
return nil, s3errors.GetAPIError(s3errors.ErrInvalidVersion)
|
|
}
|
|
return objects[i:], nil
|
|
}
|
|
}
|
|
|
|
// don't use nil as empty slice to be consistent with `return objects[j+1:], nil` above
|
|
// that can be empty
|
|
return []*data.ExtendedObjectInfo{}, nil
|
|
}
|
|
|
|
func triageVersions(objVersions []*data.ExtendedObjectInfo) ([]*data.ExtendedObjectInfo, []*data.ExtendedObjectInfo) {
|
|
if len(objVersions) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
var resVersion []*data.ExtendedObjectInfo
|
|
var resDelMarkVersions []*data.ExtendedObjectInfo
|
|
|
|
for _, version := range objVersions {
|
|
if version.NodeVersion.IsDeleteMarker() {
|
|
resDelMarkVersions = append(resDelMarkVersions, version)
|
|
} else {
|
|
resVersion = append(resVersion, version)
|
|
}
|
|
}
|
|
|
|
return resVersion, resDelMarkVersions
|
|
}
|