|
|
|
@ -37,6 +37,7 @@ import ( |
|
|
|
|
"github.com/minio/minio/cmd/crypto" |
|
|
|
|
xhttp "github.com/minio/minio/cmd/http" |
|
|
|
|
"github.com/minio/minio/cmd/logger" |
|
|
|
|
"github.com/minio/minio/pkg/bucket/lifecycle" |
|
|
|
|
objectlock "github.com/minio/minio/pkg/bucket/object/lock" |
|
|
|
|
"github.com/minio/minio/pkg/bucket/policy" |
|
|
|
|
"github.com/minio/minio/pkg/bucket/replication" |
|
|
|
@ -404,8 +405,18 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter, |
|
|
|
|
if api.CacheAPI() != nil { |
|
|
|
|
getObjectInfoFn = api.CacheAPI().GetObjectInfo |
|
|
|
|
} |
|
|
|
|
var ( |
|
|
|
|
hasLockEnabled, hasLifecycleConfig bool |
|
|
|
|
goi ObjectInfo |
|
|
|
|
gerr error |
|
|
|
|
) |
|
|
|
|
replicateDeletes := hasReplicationRules(ctx, bucket, deleteObjects.Objects) |
|
|
|
|
|
|
|
|
|
if rcfg, _ := globalBucketObjectLockSys.Get(bucket); rcfg.LockEnabled { |
|
|
|
|
hasLockEnabled = true |
|
|
|
|
} |
|
|
|
|
if _, err := globalBucketMetadataSys.GetLifecycleConfig(bucket); err == nil { |
|
|
|
|
hasLifecycleConfig = true |
|
|
|
|
} |
|
|
|
|
dErrs := make([]DeleteError, len(deleteObjects.Objects)) |
|
|
|
|
for index, object := range deleteObjects.Objects { |
|
|
|
|
if apiErrCode := checkRequestAuthType(ctx, r, policy.DeleteObjectAction, bucket, object.ObjectName); apiErrCode != ErrNone { |
|
|
|
@ -422,29 +433,19 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter, |
|
|
|
|
} |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if object.VersionID != "" { |
|
|
|
|
if rcfg, _ := globalBucketObjectLockSys.Get(bucket); rcfg.LockEnabled { |
|
|
|
|
if apiErrCode := enforceRetentionBypassForDelete(ctx, r, bucket, object, getObjectInfoFn); apiErrCode != ErrNone { |
|
|
|
|
apiErr := errorCodes.ToAPIErr(apiErrCode) |
|
|
|
|
dErrs[index] = DeleteError{ |
|
|
|
|
Code: apiErr.Code, |
|
|
|
|
Message: apiErr.Description, |
|
|
|
|
Key: object.ObjectName, |
|
|
|
|
if replicateDeletes || hasLockEnabled || hasLifecycleConfig { |
|
|
|
|
goi, gerr = getObjectInfoFn(ctx, bucket, object.ObjectName, ObjectOptions{ |
|
|
|
|
VersionID: object.VersionID, |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if hasLifecycleConfig && gerr == nil { |
|
|
|
|
object.PurgeTransitioned = goi.TransitionStatus |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Avoid duplicate objects, we use map to filter them out.
|
|
|
|
|
if _, ok := objectsToDelete[object]; !ok { |
|
|
|
|
if replicateDeletes { |
|
|
|
|
delMarker, replicate := checkReplicateDelete(ctx, getObjectInfoFn, bucket, ObjectToDelete{ |
|
|
|
|
delMarker, replicate := checkReplicateDelete(ctx, bucket, ObjectToDelete{ |
|
|
|
|
ObjectName: object.ObjectName, |
|
|
|
|
VersionID: object.VersionID, |
|
|
|
|
}) |
|
|
|
|
}, goi, gerr) |
|
|
|
|
if replicate { |
|
|
|
|
if object.VersionID != "" { |
|
|
|
|
object.VersionPurgeStatus = Pending |
|
|
|
@ -456,6 +457,23 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if object.VersionID != "" { |
|
|
|
|
if hasLockEnabled { |
|
|
|
|
if apiErrCode := enforceRetentionBypassForDelete(ctx, r, bucket, object, goi, gerr); apiErrCode != ErrNone { |
|
|
|
|
apiErr := errorCodes.ToAPIErr(apiErrCode) |
|
|
|
|
dErrs[index] = DeleteError{ |
|
|
|
|
Code: apiErr.Code, |
|
|
|
|
Message: apiErr.Description, |
|
|
|
|
Key: object.ObjectName, |
|
|
|
|
VersionID: object.VersionID, |
|
|
|
|
} |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Avoid duplicate objects, we use map to filter them out.
|
|
|
|
|
if _, ok := objectsToDelete[object]; !ok { |
|
|
|
|
objectsToDelete[object] = index |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -475,24 +493,17 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter, |
|
|
|
|
Versioned: globalBucketVersioningSys.Enabled(bucket), |
|
|
|
|
VersionSuspended: globalBucketVersioningSys.Suspended(bucket), |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
deletedObjects := make([]DeletedObject, len(deleteObjects.Objects)) |
|
|
|
|
for i := range errs { |
|
|
|
|
var dindex int |
|
|
|
|
if replicateDeletes { |
|
|
|
|
dindex = objectsToDelete[ObjectToDelete{ |
|
|
|
|
|
|
|
|
|
dindex := objectsToDelete[ObjectToDelete{ |
|
|
|
|
ObjectName: dObjects[i].ObjectName, |
|
|
|
|
VersionID: dObjects[i].VersionID, |
|
|
|
|
DeleteMarkerVersionID: dObjects[i].DeleteMarkerVersionID, |
|
|
|
|
VersionPurgeStatus: dObjects[i].VersionPurgeStatus, |
|
|
|
|
DeleteMarkerReplicationStatus: dObjects[i].DeleteMarkerReplicationStatus, |
|
|
|
|
PurgeTransitioned: dObjects[i].PurgeTransitioned, |
|
|
|
|
}] |
|
|
|
|
} else { |
|
|
|
|
dindex = objectsToDelete[ObjectToDelete{ |
|
|
|
|
ObjectName: dObjects[i].ObjectName, |
|
|
|
|
VersionID: dObjects[i].VersionID, |
|
|
|
|
}] |
|
|
|
|
} |
|
|
|
|
if errs[i] == nil || isErrObjectNotFound(errs[i]) || isErrVersionNotFound(errs[i]) { |
|
|
|
|
if replicateDeletes { |
|
|
|
|
dObjects[i].DeleteMarkerReplicationStatus = deleteList[i].DeleteMarkerReplicationStatus |
|
|
|
@ -532,6 +543,19 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter, |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if hasLifecycleConfig && dobj.PurgeTransitioned == lifecycle.TransitionComplete { // clean up transitioned tier
|
|
|
|
|
action := lifecycle.DeleteAction |
|
|
|
|
if dobj.VersionID != "" { |
|
|
|
|
action = lifecycle.DeleteVersionAction |
|
|
|
|
} |
|
|
|
|
deleteTransitionedObject(ctx, newObjectLayerFn(), bucket, dobj.ObjectName, lifecycle.ObjectOpts{ |
|
|
|
|
Name: dobj.ObjectName, |
|
|
|
|
VersionID: dobj.VersionID, |
|
|
|
|
DeleteMarker: dobj.DeleteMarker, |
|
|
|
|
}, action, true) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
// Notify deleted event for objects.
|
|
|
|
|
for _, dobj := range deletedObjects { |
|
|
|
|