如何一次从 Amazon S3 上的多个对象中删除删除标记

How to Remove Delete Markers from Multiple Objects on Amazon S3 at once

我有一个启用了版本控制的 Amazon S3 存储桶。由于生命周期策略配置错误,此存储桶中的许多对象都添加了删除标记。

我可以从 S3 控制台删除这些标记以恢复这些对象的以前版本,但是有足够的对象使得在 Web 控制台上手动执行此操作非常耗时。

有没有办法找到 S3 存储桶中的所有删除标记并将其删除,从而恢复该存储桶中的所有文件?理想情况下,我想从控制台本身执行此操作,但如果这是唯一的方法,我会很乐意编写脚本或使用亚马逊 CLI 工具来执行此操作。

谢谢!

您需要编写一个程序来:

  • 遍历 Amazon S3 存储桶中的所有对象
  • 检索每个对象的每个版本的版本 ID
  • 删除删除标记

这可以使用 SDK 轻松完成,例如 boto

也可以使用AWS Command-Line Interface (CLI),但是你必须围绕它构建一个脚本来捕获ID,然后删除标记。

我刚刚写了一个程序(使用boto)来解决同样的问题:

from boto.s3 import deletemarker
from boto.s3.connection import S3Connection
from boto.s3.key import Key

def restore_bucket(bucket_name): 
    bucket = conn.get_bucket(bucket_name)
    for version in bucket.list_versions():
        if isinstance(version, deletemarker.DeleteMarker) and version.is_latest:
            bucket.delete_key(version.name, version_id=version.version_id)

如果您需要恢复版本化存储桶中的文件夹,可以找到我编写的程序的其余部分here.

使用它来恢复特定文件夹中的文件。我在脚本中使用了 aws cli 命令。提供输入为: sh scriptname.sh 桶名 path/to/a/folder

**Script:**
#!/bin/bash
#please provide the bucketname and path to destination folder to restore
# Remove all versions and delete markers for each object
 aws s3api list-object-versions --bucket  --prefix  --output text | 
 grep "DELETEMARKERS" | while read obj
   do
        KEY=$( echo $obj| awk '{print }')
        VERSION_ID=$( echo $obj | awk '{print }')
        echo $KEY
        echo $VERSION_ID
        aws s3api delete-object --bucket  --key $KEY --version-id $VERSION_ID

   done

编辑:$VERSION_ID 放在脚本中的正确位置

定义变量

PROFILE="personal"
REGION="eu-west-1"
BUCKET="mysql-backend-backups-prod"

一次删除 DeleteMarkers

aws --profile $PROFILE s3api delete-objects \
    --region $REGION \
    --bucket $BUCKET \
    --delete "$(aws --profile $PROFILE s3api list-object-versions \
                    --region $REGION \
                    --bucket $BUCKET \
                    --output=json \
                    --query='{Objects: DeleteMarkers[].{Key:Key,VersionId:VersionId}}')"

一次删除版本

aws --profile $PROFILE s3api delete-objects \
    --region $REGION \
    --bucket $BUCKET \
    --delete "$(aws --profile $PROFILE s3api list-object-versions \
                    --region $REGION \
                    --bucket $BUCKET \
                    --output=json \
                    --query='{Objects: Versions[].{Key:Key,VersionId:VersionId}}')"

然后删除 S3 存储桶

aws --profile $PROFILE s3api delete-bucket \
    --region $REGION \
    --bucket $BUCKET

这是一个示例 Python 实施:

import boto3
import botocore

BUCKET_NAME = 'BUCKET_NAME'
s3 = boto3.resource('s3')


def main():
    bucket = s3.Bucket(BUCKET_NAME)
    versions = bucket.object_versions

    for version in versions.all():
        if is_delete_marker(version):
             version.delete()


def is_delete_marker(version):
    try:
        # note head() is faster than get()
        version.head()
        return False
    except botocore.exceptions.ClientError as e:
        if 'x-amz-delete-marker' in e.response['ResponseMetadata']['HTTPHeaders']:
            return True
        # an older version of the key but not a DeleteMarker
        elif '404' == e.response['Error']['Code']:
            return False


if __name__ == '__main__':
    main()

有关此答案的一些上下文,请参阅: https://docs.aws.amazon.com/AmazonS3/latest/dev/DeleteMarker.html

If you try to get an object and its current version is a delete marker, Amazon S3 responds with:

  • A 404 (Object not found) error
  • A response header, x-amz-delete-marker: true

The response header tells you that the object accessed was a delete marker. This response header never returns false; if the value is false, Amazon S3 does not include this response header in the response.

The only way to list delete markers (and other versions of an object) is by using the versions subresource in a GET Bucket versions request. A simple GET does not retrieve delete marker objects.

不幸的是,尽管 https://github.com/boto/botocore/issues/674 中写了什么,检查 ObjectVersion.size is None 是否不是确定版本是否为删除标记的可靠方法,因为它对于以前删除的版本也是如此文件夹键。

目前,boto3 缺少一种直接的方法来确定 ObjectVersion 是否为 DeleteMarker。参见 https://github.com/boto/boto3/issues/1769

但是,ObjectVersion.head().Get() 操作将在作为 DeleteMarker 的 ObjectVersion 上抛出异常。捕获此异常可能是确定 ObjectVersion 是否为 DeleteMarker 的唯一可靠方法。

几周前我一直在处理这个问题。

最后我设法在 PHP 中生成了一个函数,它删除了前缀中最新版本文件的 'deleted markers'。 就个人而言,它工作得很好,并且在这个脚本的传递中,遍历所有前缀,我通过无意中删除了许多 s3 对象来修复我自己的错误。

我将我的实现留在下面的 PHP 中:

private function restore_files($file)
{
    $storage = get_storage()->getDriver()->getAdapter()->getClient();
    $bucket_name = 'my_bucket_name';
    $s3_path=$file->s3_path;

    $restore_folder_path = pathinfo($s3_path, PATHINFO_DIRNAME);

    $data = $storage->listObjectVersions([
        'Bucket' => $bucket_name,
        'Prefix' => $restore_folder_path,
    ]);

    $data_array = $data->toArray();
    $deleteMarkers = $data_array['DeleteMarkers'];

    foreach ($deleteMarkers as $key => $delete_marker) {
        if ($delete_marker["IsLatest"]) {
            $objkey = $delete_marker["Key"];
            $objVersionId = $delete_marker["VersionId"];

            $delete_response = $storage-> deleteObjectAsync([
                'Bucket' => $bucket_name,
                'Key' => $objkey,
                'VersionId' => $objVersionId
            ]);
        }
    }
}

关于脚本的一些注意事项:

  1. 代码是使用 Laravel 框架实现的,因此,在变量 $storage 中,我单独获得了 PHP SDK,而没有使用所有 laravel 的包装器。因此,$storage 变量是 S3 SDK 的客户端对象。这是我用过的documentation
  2. 函数接收的 $file 参数是一个在其属性中具有 s3_path 的对象。因此,在 $restore_folder_path 变量中,我得到了对象 s3 路径的前缀。
  3. 最后,我得到了 s3 中前缀内的所有对象。我遍历 DeleteMarkers 列表,并询问当前对象是否是最后删除的标记。如果是,我创建一个 post 到 deleteObject 函数,其中包含我要删除的对象的特定 ID,它是已删除的标记。 This is the way s3 documentation specify to remove the deleted marker

设置生命周期规则以在特定天数后删除它们。否则,每 1000 个对象列表将花费 0.005 美元。

所以最有效的方法是设置生命周期规则。

这是一步一步的方法。 https://docs.aws.amazon.com/AmazonS3/latest/user-guide/create-lifecycle.html

I checked the file size. 
Marker size is 'None'
Remove all Marker.
import boto3

default_session=boto3.session.Session(profile_name="default")
s3_re=default_session.resource(service_name="s3", region_name="ap-northeast-2")
for each_bucket in s3_re.buckets.all():
    bucket_name = each_bucket.name
    s3 = boto3.resource('s3')
    bucket = s3.Bucket(bucket_name)
    version = bucket.object_versions
    for ver in version.all():
        if str(ver.size) in 'None':
            delete_file = ver.delete()
            print(delete_file)
        else:
            pass

上面的大多数版本在大桶上都非常慢,因为它们使用 delete-object 而不是 delete-objects。这里是 bash 版本的变体,它使用 awk 一次发出 100 个请求:

编辑:刚看到@Viacheslav 的版本,它也使用 delete-objects 并且干净整洁,但由于行长问题,大量标记会失败。

#!/bin/bash

bucket=
prefix=

aws s3api list-object-versions \
    --bucket "$bucket" \
    --prefix "$prefix" \
    --query 'DeleteMarkers[][Key,VersionId]' \
    --output text |
awk '{ acc = acc "{Key="  ",VersionId="  "}," }
     NR % 100 == 0 {print "Objects=[" acc "],Quiet=False"; acc="" }
     END { print "Objects=[" acc "],Quiet=False" }' |
while read batch; do
    aws s3api delete-objects --bucket "$bucket" --delete "$batch" --output text
done