@@ -37,6 +37,7 @@ import (
3737 "github.com/dustin/go-humanize"
3838 "github.com/lithammer/shortuuid/v4"
3939 "github.com/minio/madmin-go/v3"
40+ "github.com/minio/minio-go/v7"
4041 miniogo "github.com/minio/minio-go/v7"
4142 "github.com/minio/minio-go/v7/pkg/credentials"
4243 "github.com/minio/minio-go/v7/pkg/encrypt"
@@ -269,6 +270,10 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
269270 }
270271 rnd := rand .New (rand .NewSource (time .Now ().UnixNano ()))
271272
273+ isTags := len (r .Flags .Filter .Tags ) != 0
274+ isMetadata := len (r .Flags .Filter .Metadata ) != 0
275+ isStorageClassOnly := len (r .Flags .Filter .Metadata ) == 1 && strings .EqualFold (r .Flags .Filter .Metadata [0 ].Key , xhttp .AmzStorageClass )
276+
272277 skip := func (oi ObjectInfo ) (ok bool ) {
273278 if r .Flags .Filter .OlderThan > 0 && time .Since (oi .ModTime ) < r .Flags .Filter .OlderThan {
274279 // skip all objects that are newer than specified older duration
@@ -289,7 +294,8 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
289294 // skip all objects that are created after the specified time.
290295 return true
291296 }
292- if len (r .Flags .Filter .Tags ) > 0 {
297+
298+ if isTags {
293299 // Only parse object tags if tags filter is specified.
294300 tagMap := map [string ]string {}
295301 tagStr := oi .UserTags
@@ -312,23 +318,19 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
312318 return false
313319 }
314320
315- if len (r .Flags .Filter .Metadata ) > 0 {
316- for _ , kv := range r .Flags .Filter .Metadata {
317- for k , v := range oi .UserDefined {
318- if ! stringsHasPrefixFold (k , "x-amz-meta-" ) && ! isStandardHeader (k ) {
319- continue
320- }
321- // We only need to match x-amz-meta or standardHeaders
322- if kv .Match (BatchJobKV {Key : k , Value : v }) {
323- return true
324- }
321+ for _ , kv := range r .Flags .Filter .Metadata {
322+ for k , v := range oi .UserDefined {
323+ if ! stringsHasPrefixFold (k , "x-amz-meta-" ) && ! isStandardHeader (k ) {
324+ continue
325+ }
326+ // We only need to match x-amz-meta or standardHeaders
327+ if kv .Match (BatchJobKV {Key : k , Value : v }) {
328+ return true
325329 }
326330 }
327-
328- // None of the provided metadata filters match skip the object.
329- return false
330331 }
331332
333+ // None of the provided filters match
332334 return false
333335 }
334336
@@ -383,17 +385,32 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
383385 for obj := range objInfoCh {
384386 oi := toObjectInfo (r .Source .Bucket , obj .Key , obj )
385387 if ! minioSrc {
386- oi2 , err := c .StatObject (ctx , r .Source .Bucket , obj .Key , miniogo.StatObjectOptions {})
387- if err == nil {
388- oi = toObjectInfo (r .Source .Bucket , obj .Key , oi2 )
389- } else {
390- if isErrMethodNotAllowed (ErrorRespToObjectError (err , r .Source .Bucket , obj .Key )) ||
391- isErrObjectNotFound (ErrorRespToObjectError (err , r .Source .Bucket , obj .Key )) {
388+ // Check if metadata filter was requested and it is expected to have
389+ // all user metadata or just storageClass. If its only storageClass
390+ // List() already returns relevant information for filter to be applied.
391+ if isMetadata && ! isStorageClassOnly {
392+ oi2 , err := c .StatObject (ctx , r .Source .Bucket , obj .Key , miniogo.StatObjectOptions {})
393+ if err == nil {
394+ oi = toObjectInfo (r .Source .Bucket , obj .Key , oi2 )
395+ } else {
396+ if ! isErrMethodNotAllowed (ErrorRespToObjectError (err , r .Source .Bucket , obj .Key )) &&
397+ ! isErrObjectNotFound (ErrorRespToObjectError (err , r .Source .Bucket , obj .Key )) {
398+ logger .LogIf (ctx , err )
399+ }
400+ continue
401+ }
402+ }
403+ if isTags {
404+ tags , err := c .GetObjectTagging (ctx , r .Source .Bucket , obj .Key , minio.GetObjectTaggingOptions {})
405+ if err == nil {
406+ oi .UserTags = tags .String ()
407+ } else {
408+ if ! isErrMethodNotAllowed (ErrorRespToObjectError (err , r .Source .Bucket , obj .Key )) &&
409+ ! isErrObjectNotFound (ErrorRespToObjectError (err , r .Source .Bucket , obj .Key )) {
410+ logger .LogIf (ctx , err )
411+ }
392412 continue
393413 }
394- logger .LogIf (ctx , err )
395- cancel ()
396- return err
397414 }
398415 }
399416 if skip (oi ) {
@@ -481,17 +498,25 @@ func toObjectInfo(bucket, object string, objInfo miniogo.ObjectInfo) ObjectInfo
481498 ReplicationStatusInternal : objInfo .ReplicationStatus ,
482499 UserTags : tags .String (),
483500 }
501+
484502 oi .UserDefined = make (map [string ]string , len (objInfo .Metadata ))
485503 for k , v := range objInfo .Metadata {
486504 oi .UserDefined [k ] = v [0 ]
487505 }
506+
488507 ce , ok := oi .UserDefined [xhttp .ContentEncoding ]
489508 if ! ok {
490509 ce , ok = oi .UserDefined [strings .ToLower (xhttp .ContentEncoding )]
491510 }
492511 if ok {
493512 oi .ContentEncoding = ce
494513 }
514+
515+ _ , ok = oi .UserDefined [xhttp .AmzStorageClass ]
516+ if ! ok {
517+ oi .UserDefined [xhttp .AmzStorageClass ] = objInfo .StorageClass
518+ }
519+
495520 return oi
496521}
497522
@@ -821,7 +846,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
821846 }
822847 rnd := rand .New (rand .NewSource (time .Now ().UnixNano ()))
823848
824- skip := func (info FileInfo ) (ok bool ) {
849+ selectObj := func (info FileInfo ) (ok bool ) {
825850 if r .Flags .Filter .OlderThan > 0 && time .Since (info .ModTime ) < r .Flags .Filter .OlderThan {
826851 // skip all objects that are newer than specified older duration
827852 return false
@@ -931,7 +956,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
931956 results := make (chan ObjectInfo , 100 )
932957 if err := api .Walk (ctx , r .Source .Bucket , r .Source .Prefix , results , ObjectOptions {
933958 WalkMarker : lastObject ,
934- WalkFilter : skip ,
959+ WalkFilter : selectObj ,
935960 }); err != nil {
936961 cancel ()
937962 // Do not need to retry if we can't list objects on source.
0 commit comments