mirror of
https://github.com/minio/minio.git
synced 2025-03-30 17:23:42 -04:00
gcs: Use Pager to iterate results in ListObjectsV1/V2 (#6162)
Fixes #6052
This commit is contained in:
parent
6c93c60424
commit
3ec4738955
@ -554,16 +554,16 @@ func isGCSMarker(marker string) bool {
|
|||||||
|
|
||||||
// ListObjects - lists all blobs in GCS bucket filtered by prefix
|
// ListObjects - lists all blobs in GCS bucket filtered by prefix
|
||||||
func (l *gcsGateway) ListObjects(ctx context.Context, bucket string, prefix string, marker string, delimiter string, maxKeys int) (minio.ListObjectsInfo, error) {
|
func (l *gcsGateway) ListObjects(ctx context.Context, bucket string, prefix string, marker string, delimiter string, maxKeys int) (minio.ListObjectsInfo, error) {
|
||||||
|
if maxKeys == 0 {
|
||||||
|
return minio.ListObjectsInfo{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
it := l.client.Bucket(bucket).Objects(l.ctx, &storage.Query{
|
it := l.client.Bucket(bucket).Objects(l.ctx, &storage.Query{
|
||||||
Delimiter: delimiter,
|
Delimiter: delimiter,
|
||||||
Prefix: prefix,
|
Prefix: prefix,
|
||||||
Versions: false,
|
Versions: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
isTruncated := false
|
|
||||||
nextMarker := ""
|
|
||||||
prefixes := []string{}
|
|
||||||
|
|
||||||
// To accommodate S3-compatible applications using
|
// To accommodate S3-compatible applications using
|
||||||
// ListObjectsV1 to use object keys as markers to control the
|
// ListObjectsV1 to use object keys as markers to control the
|
||||||
// listing of objects, we use the following encoding scheme to
|
// listing of objects, we use the following encoding scheme to
|
||||||
@ -574,83 +574,86 @@ func (l *gcsGateway) ListObjects(ctx context.Context, bucket string, prefix stri
|
|||||||
// prefixing "{minio}" to the GCS continuation token,
|
// prefixing "{minio}" to the GCS continuation token,
|
||||||
// e.g, "{minio}CgRvYmoz"
|
// e.g, "{minio}CgRvYmoz"
|
||||||
//
|
//
|
||||||
// - Application supplied markers are used as-is to list
|
// - Application supplied markers are transformed to a
|
||||||
// object keys that appear after it in the lexicographical order.
|
// GCS continuation token.
|
||||||
|
|
||||||
// If application is using GCS continuation token we should
|
// If application is using GCS continuation token we should
|
||||||
// strip the gcsTokenPrefix we added.
|
// strip the gcsTokenPrefix we added.
|
||||||
gcsMarker := isGCSMarker(marker)
|
token := ""
|
||||||
if gcsMarker {
|
if marker != "" {
|
||||||
it.PageInfo().Token = strings.TrimPrefix(marker, gcsTokenPrefix)
|
if isGCSMarker(marker) {
|
||||||
|
token = strings.TrimPrefix(marker, gcsTokenPrefix)
|
||||||
|
} else {
|
||||||
|
token = toGCSPageToken(marker)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
nextMarker := ""
|
||||||
|
|
||||||
it.PageInfo().MaxSize = maxKeys
|
var prefixes []string
|
||||||
|
var objects []minio.ObjectInfo
|
||||||
|
var nextPageToken string
|
||||||
|
var err error
|
||||||
|
|
||||||
objects := []minio.ObjectInfo{}
|
pager := iterator.NewPager(it, maxKeys, token)
|
||||||
for {
|
for {
|
||||||
if len(objects) >= maxKeys {
|
gcsObjects := make([]*storage.ObjectAttrs, 0)
|
||||||
// check if there is one next object and
|
nextPageToken, err = pager.NextPage(&gcsObjects)
|
||||||
// if that one next object is our hidden
|
|
||||||
// metadata folder, then just break
|
|
||||||
// otherwise we've truncated the output
|
|
||||||
attrs, _ := it.Next()
|
|
||||||
if attrs != nil && attrs.Prefix == minio.GatewayMinioSysTmp {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
isTruncated = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
attrs, err := it.Next()
|
|
||||||
if err == iterator.Done {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return minio.ListObjectsInfo{}, gcsToObjectError(err, bucket, prefix)
|
return minio.ListObjectsInfo{}, gcsToObjectError(err, bucket, prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
nextMarker = toGCSPageToken(attrs.Name)
|
for _, attrs := range gcsObjects {
|
||||||
|
|
||||||
if attrs.Prefix == minio.GatewayMinioSysTmp {
|
// Due to minio.GatewayMinioSysTmp keys being skipped, the number of objects + prefixes
|
||||||
// We don't return our metadata prefix.
|
// returned may not total maxKeys. This behavior is compatible with the S3 spec which
|
||||||
continue
|
// allows the response to include less keys than maxKeys.
|
||||||
}
|
if attrs.Prefix == minio.GatewayMinioSysTmp {
|
||||||
if !strings.HasPrefix(prefix, minio.GatewayMinioSysTmp) {
|
// We don't return our metadata prefix.
|
||||||
// If client lists outside gcsMinioPath then we filter out gcsMinioPath/* entries.
|
|
||||||
// But if the client lists inside gcsMinioPath then we return the entries in gcsMinioPath/
|
|
||||||
// which will be helpful to observe the "directory structure" for debugging purposes.
|
|
||||||
if strings.HasPrefix(attrs.Prefix, minio.GatewayMinioSysTmp) ||
|
|
||||||
strings.HasPrefix(attrs.Name, minio.GatewayMinioSysTmp) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
if !strings.HasPrefix(prefix, minio.GatewayMinioSysTmp) {
|
||||||
if attrs.Prefix != "" {
|
// If client lists outside gcsMinioPath then we filter out gcsMinioPath/* entries.
|
||||||
prefixes = append(prefixes, attrs.Prefix)
|
// But if the client lists inside gcsMinioPath then we return the entries in gcsMinioPath/
|
||||||
continue
|
// which will be helpful to observe the "directory structure" for debugging purposes.
|
||||||
}
|
if strings.HasPrefix(attrs.Prefix, minio.GatewayMinioSysTmp) ||
|
||||||
if !gcsMarker && attrs.Name <= marker {
|
strings.HasPrefix(attrs.Name, minio.GatewayMinioSysTmp) {
|
||||||
// if user supplied a marker don't append
|
continue
|
||||||
// objects until we reach marker (and skip it).
|
}
|
||||||
continue
|
}
|
||||||
|
|
||||||
|
if attrs.Prefix != "" {
|
||||||
|
prefixes = append(prefixes, attrs.Prefix)
|
||||||
|
} else {
|
||||||
|
objects = append(objects, fromGCSAttrsToObjectInfo(attrs))
|
||||||
|
}
|
||||||
|
|
||||||
|
// The NextMarker property should only be set in the response if a delimiter is used
|
||||||
|
if delimiter != "" {
|
||||||
|
if attrs.Prefix > nextMarker {
|
||||||
|
nextMarker = attrs.Prefix
|
||||||
|
} else if attrs.Name > nextMarker {
|
||||||
|
nextMarker = attrs.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
objects = append(objects, minio.ObjectInfo{
|
// Exit the loop if at least one item can be returned from
|
||||||
Name: attrs.Name,
|
// the current page or there are no more pages available
|
||||||
Bucket: attrs.Bucket,
|
if nextPageToken == "" || len(prefixes)+len(objects) > 0 {
|
||||||
ModTime: attrs.Updated,
|
break
|
||||||
Size: attrs.Size,
|
}
|
||||||
ETag: minio.ToS3ETag(fmt.Sprintf("%d", attrs.CRC32C)),
|
}
|
||||||
UserDefined: attrs.Metadata,
|
|
||||||
ContentType: attrs.ContentType,
|
if nextPageToken == "" {
|
||||||
ContentEncoding: attrs.ContentEncoding,
|
nextMarker = ""
|
||||||
})
|
} else if nextMarker != "" {
|
||||||
|
nextMarker = gcsTokenPrefix + toGCSPageToken(nextMarker)
|
||||||
}
|
}
|
||||||
|
|
||||||
return minio.ListObjectsInfo{
|
return minio.ListObjectsInfo{
|
||||||
IsTruncated: isTruncated,
|
IsTruncated: nextPageToken != "",
|
||||||
NextMarker: gcsTokenPrefix + nextMarker,
|
NextMarker: nextMarker,
|
||||||
Prefixes: prefixes,
|
Prefixes: prefixes,
|
||||||
Objects: objects,
|
Objects: objects,
|
||||||
}, nil
|
}, nil
|
||||||
@ -658,8 +661,8 @@ func (l *gcsGateway) ListObjects(ctx context.Context, bucket string, prefix stri
|
|||||||
|
|
||||||
// ListObjectsV2 - lists all blobs in GCS bucket filtered by prefix
|
// ListObjectsV2 - lists all blobs in GCS bucket filtered by prefix
|
||||||
func (l *gcsGateway) ListObjectsV2(ctx context.Context, bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (minio.ListObjectsV2Info, error) {
|
func (l *gcsGateway) ListObjectsV2(ctx context.Context, bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (minio.ListObjectsV2Info, error) {
|
||||||
if continuationToken == "" && startAfter != "" {
|
if maxKeys == 0 {
|
||||||
continuationToken = toGCSPageToken(startAfter)
|
return minio.ListObjectsV2Info{ContinuationToken: continuationToken}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
it := l.client.Bucket(bucket).Objects(l.ctx, &storage.Query{
|
it := l.client.Bucket(bucket).Objects(l.ctx, &storage.Query{
|
||||||
@ -668,61 +671,62 @@ func (l *gcsGateway) ListObjectsV2(ctx context.Context, bucket, prefix, continua
|
|||||||
Versions: false,
|
Versions: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
isTruncated := false
|
token := continuationToken
|
||||||
|
if token == "" && startAfter != "" {
|
||||||
if continuationToken != "" {
|
token = toGCSPageToken(startAfter)
|
||||||
// If client sends continuationToken, set it
|
|
||||||
it.PageInfo().Token = continuationToken
|
|
||||||
} else {
|
|
||||||
// else set the continuationToken to return
|
|
||||||
continuationToken = it.PageInfo().Token
|
|
||||||
if continuationToken != "" {
|
|
||||||
// If GCS SDK sets continuationToken, it means there are more than maxKeys in the current page
|
|
||||||
// and the response will be truncated
|
|
||||||
isTruncated = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var prefixes []string
|
var prefixes []string
|
||||||
var objects []minio.ObjectInfo
|
var objects []minio.ObjectInfo
|
||||||
|
var nextPageToken string
|
||||||
|
var err error
|
||||||
|
|
||||||
for keyCount := 0; keyCount < maxKeys; keyCount++ {
|
pager := iterator.NewPager(it, maxKeys, token)
|
||||||
attrs, err := it.Next()
|
for {
|
||||||
if err == iterator.Done {
|
gcsObjects := make([]*storage.ObjectAttrs, 0)
|
||||||
break
|
nextPageToken, err = pager.NextPage(&gcsObjects)
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
return minio.ListObjectsV2Info{}, gcsToObjectError(err, bucket, prefix)
|
return minio.ListObjectsV2Info{}, gcsToObjectError(err, bucket, prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
if attrs.Prefix == minio.GatewayMinioSysTmp {
|
for _, attrs := range gcsObjects {
|
||||||
// We don't return our metadata prefix.
|
|
||||||
continue
|
// Due to minio.GatewayMinioSysTmp keys being skipped, the number of objects + prefixes
|
||||||
}
|
// returned may not total maxKeys. This behavior is compatible with the S3 spec which
|
||||||
if !strings.HasPrefix(prefix, minio.GatewayMinioSysTmp) {
|
// allows the response to include less keys than maxKeys.
|
||||||
// If client lists outside gcsMinioPath then we filter out gcsMinioPath/* entries.
|
if attrs.Prefix == minio.GatewayMinioSysTmp {
|
||||||
// But if the client lists inside gcsMinioPath then we return the entries in gcsMinioPath/
|
// We don't return our metadata prefix.
|
||||||
// which will be helpful to observe the "directory structure" for debugging purposes.
|
|
||||||
if strings.HasPrefix(attrs.Prefix, minio.GatewayMinioSysTmp) ||
|
|
||||||
strings.HasPrefix(attrs.Name, minio.GatewayMinioSysTmp) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if !strings.HasPrefix(prefix, minio.GatewayMinioSysTmp) {
|
||||||
|
// If client lists outside gcsMinioPath then we filter out gcsMinioPath/* entries.
|
||||||
|
// But if the client lists inside gcsMinioPath then we return the entries in gcsMinioPath/
|
||||||
|
// which will be helpful to observe the "directory structure" for debugging purposes.
|
||||||
|
if strings.HasPrefix(attrs.Prefix, minio.GatewayMinioSysTmp) ||
|
||||||
|
strings.HasPrefix(attrs.Name, minio.GatewayMinioSysTmp) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if attrs.Prefix != "" {
|
||||||
|
prefixes = append(prefixes, attrs.Prefix)
|
||||||
|
} else {
|
||||||
|
objects = append(objects, fromGCSAttrsToObjectInfo(attrs))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if attrs.Prefix != "" {
|
// Exit the loop if at least one item can be returned from
|
||||||
prefixes = append(prefixes, attrs.Prefix)
|
// the current page or there are no more pages available
|
||||||
continue
|
if nextPageToken == "" || len(prefixes)+len(objects) > 0 {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
objects = append(objects, fromGCSAttrsToObjectInfo(attrs))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return minio.ListObjectsV2Info{
|
return minio.ListObjectsV2Info{
|
||||||
IsTruncated: isTruncated,
|
IsTruncated: nextPageToken != "",
|
||||||
ContinuationToken: continuationToken,
|
ContinuationToken: continuationToken,
|
||||||
NextContinuationToken: continuationToken,
|
NextContinuationToken: nextPageToken,
|
||||||
Prefixes: prefixes,
|
Prefixes: prefixes,
|
||||||
Objects: objects,
|
Objects: objects,
|
||||||
}, nil
|
}, nil
|
||||||
|
Loading…
x
Reference in New Issue
Block a user