From 64a8f2e55497755dc0befdd79991c5527436cba7 Mon Sep 17 00:00:00 2001 From: Poorna Date: Tue, 4 Feb 2025 00:35:55 -0800 Subject: [PATCH] replication: default tag timestamps in CopyObject call (#20891) If object is uploaded with tags, the internal tagging-timestamp tracked for replication will be missing. Default to ModTime in such cases to allow tags to be synced correctly. Also fixing a regression in fetching tags and tag comparison --- cmd/bucket-replication.go | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/cmd/bucket-replication.go b/cmd/bucket-replication.go index f278c91ce..d909ff40c 100644 --- a/cmd/bucket-replication.go +++ b/cmd/bucket-replication.go @@ -958,7 +958,11 @@ func getReplicationAction(oi1 ObjectInfo, oi2 minio.ObjectInfo, opType replicati } t, _ := tags.ParseObjectTags(oi1.UserTags) - if (oi2.UserTagCount > 0 && !reflect.DeepEqual(oi2.UserTags, t.ToMap())) || (oi2.UserTagCount != len(t.ToMap())) { + oi2Map := make(map[string]string) + for k, v := range oi2.UserTags { + oi2Map[k] = v + } + if (oi2.UserTagCount > 0 && !reflect.DeepEqual(oi2Map, t.ToMap())) || (oi2.UserTagCount != len(t.ToMap())) { return replicateMetadata } @@ -1447,13 +1451,14 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object } rinfo.Duration = time.Since(startTime) }() - - oi, cerr := tgt.StatObject(ctx, tgt.Bucket, object, minio.StatObjectOptions{ + sOpts := minio.StatObjectOptions{ VersionID: objInfo.VersionID, Internal: minio.AdvancedGetOptions{ ReplicationProxyRequest: "false", }, - }) + } + sOpts.Set(xhttp.AmzTagDirective, "ACCESS") + oi, cerr := tgt.StatObject(ctx, tgt.Bucket, object, sOpts) if cerr == nil { rAction = getReplicationAction(objInfo, oi, ri.OpType) rinfo.ReplicationStatus = replication.Completed @@ -1538,19 +1543,30 @@ applyAction: ReplicationRequest: true, // always set this to distinguish between `mc mirror` replication and serverside }, } - if tagTmStr, ok := objInfo.UserDefined[ReservedMetadataPrefixLower+TaggingTimestamp]; ok { + // default timestamps to ModTime unless present in metadata + lkMap := caseInsensitiveMap(objInfo.UserDefined) + if _, ok := lkMap.Lookup(xhttp.AmzObjectLockLegalHold); ok { + dstOpts.Internal.LegalholdTimestamp = objInfo.ModTime + } + if _, ok := lkMap.Lookup(xhttp.AmzObjectLockRetainUntilDate); ok { + dstOpts.Internal.RetentionTimestamp = objInfo.ModTime + } + if objInfo.UserTags != "" { + dstOpts.Internal.TaggingTimestamp = objInfo.ModTime + } + if tagTmStr, ok := lkMap.Lookup(ReservedMetadataPrefixLower + TaggingTimestamp); ok { ondiskTimestamp, err := time.Parse(time.RFC3339, tagTmStr) if err == nil { dstOpts.Internal.TaggingTimestamp = ondiskTimestamp } } - if retTmStr, ok := objInfo.UserDefined[ReservedMetadataPrefixLower+ObjectLockRetentionTimestamp]; ok { + if retTmStr, ok := lkMap.Lookup(ReservedMetadataPrefixLower + ObjectLockRetentionTimestamp); ok { ondiskTimestamp, err := time.Parse(time.RFC3339, retTmStr) if err == nil { dstOpts.Internal.RetentionTimestamp = ondiskTimestamp } } - if lholdTmStr, ok := objInfo.UserDefined[ReservedMetadataPrefixLower+ObjectLockLegalHoldTimestamp]; ok { + if lholdTmStr, ok := lkMap.Lookup(ReservedMetadataPrefixLower + ObjectLockLegalHoldTimestamp); ok { ondiskTimestamp, err := time.Parse(time.RFC3339, lholdTmStr) if err == nil { dstOpts.Internal.LegalholdTimestamp = ondiskTimestamp