Объект S3 все еще находится в ведре после его успешного удаления и маркера удаления

Я создал этот контекстный менеджер для создания временного объекта в S3 в целях тестирования:

from types import TracebackType
from typing import BinaryIO, Optional, Type

import boto3


class S3Object:
    def __init__(self, file_object: BinaryIO, bucket_name: str, key: str):
        self.file_object = file_object
        self.bucket_name = bucket_name
        self.key = key
        self.s3 = boto3.client("s3")

    def __enter__(self) -> "S3Object":
        self.s3.upload_fileobj(self.file_object, self.bucket_name, self.key)
        return self

    def __exit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_val: Optional[BaseException],
        exc_tb: Optional[TracebackType],
    ) -> bool:
        delete_object_response = self.s3.delete_object(Bucket=self.bucket_name, Key=self.key)
        # Delete version marker
        self.s3.delete_object(
            Bucket=self.bucket_name, Key=self.key, VersionId=delete_object_response["VersionId"]
        )
        print("Deleted object")  # debugging
        return False  # Propagate exception

Я использую это так в одном тесте:

with S3Object(BytesIO(), bucket_name, filename) as s3_object:
    …

Странно то, что, несмотря на то, что выходные данные теста включают выходные данные отладки удаленного объекта, исходный элемент все еще находится в корзине. Из вывода cdk destroy:

Сегмент, который вы пытались удалить, не пуст. Вы должны удалить все версии в корзине.

Это определенно правильный файл, потому что я создаю уникальное имя файла на основе имени теста. Это также не похоже на проблему синхронизации, потому что это 100% воспроизводимость.

Я могу удалить файл вручную, так что особой защиты тоже нет.

После изменения кода для печати обоих ответов мне интересно, дважды ли я удалял маркер удаления? Сокращено для удобства чтения:

{'ResponseMetadata': {'HTTPStatusCode': 204, 'HTTPHeaders': {'x-amz-version-id': 'd08uv05t7lU7YKle4kMSURa_jrMU.GSL', 'x-amz-delete-marker': 'true'}}, 'DeleteMarker': True, 'VersionId': 'd08uv05t7lU7YKle4kMSURa_jrMU.GSL'}
{'ResponseMetadata': {'HTTPStatusCode': 204, 'HTTPHeaders': {'x-amz-version-id': 'd08uv05t7lU7YKle4kMSURa_jrMU.GSL', 'x-amz-delete-marker': 'true'}}, 'DeleteMarker': True, 'VersionId': 'd08uv05t7lU7YKle4kMSURa_jrMU.GSL'}

person l0b0    schedule 23.11.2020    source источник
comment
Возможно, я неправильно понимаю это, но чтобы навсегда удалить объект с версией, вы удаляете идентификатор версии объекта. Похоже, вы удаляете объект (без версии), который вставляет маркер удаления и возвращает вам идентификатор версии, а затем вы удаляете маркер удаления, что фактически возвращает объект к жизни.   -  person jarmod    schedule 23.11.2020
comment
Я думаю, что @jarmod прав. Вам следует использовать ListObjectVersions, если вы хотите навсегда удалить объект и сделать только один вызов API и предоставить идентификатор версии: docs.aws.amazon.com/AmazonS3/latest/API/   -  person Max Schenkelberg    schedule 23.11.2020


Ответы (1)


Основываясь на комментариях и другом ответе, я пришел к следующему:

from types import TracebackType
from typing import BinaryIO, Optional, Type

import boto3

DELETE_OBJECTS_MAX_KEYS = 1000


class S3Object:
    def __init__(self, file_object: BinaryIO, bucket_name: str, key: str):
        self.file_object = file_object
        self.bucket_name = bucket_name
        self.key = key
        self.s3 = boto3.client("s3")

    def __enter__(self) -> "S3Object":
        self.s3.upload_fileobj(self.file_object, self.bucket_name, self.key)
        return self

    def __exit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_val: Optional[BaseException],
        exc_tb: Optional[TracebackType],
    ) -> bool:
        version_list = []
        object_versions_paginator = self.s3.get_paginator("list_object_versions")
        for object_versions_page in object_versions_paginator.paginate(Bucket=self.bucket_name):
            for marker in object_versions_page.get("DeleteMarkers", []):
                if marker["Key"] == self.key:
                    version_list.append({"Key": self.key, "VersionId": marker["VersionId"]})
            for version in object_versions_page.get("Versions", []):
                if version["Key"] == self.key:
                    version_list.append({"Key": self.key, "VersionId": version["VersionId"]})

        for index in range(0, len(version_list), DELETE_OBJECTS_MAX_KEYS):
            response = self.s3.delete_objects(
                Bucket=self.bucket_name,
                Delete={
                    "Objects": version_list[index : index + DELETE_OBJECTS_MAX_KEYS],
                },
            )
            print(response)

        return False  # Propagate exception
person l0b0    schedule 23.11.2020