From 0bad68f9f037d32433b74748a239e136b6e869a1 Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Tue, 9 Nov 2021 18:49:29 -0100 Subject: [PATCH] S3 - create_multipart_upload - support tags (#4548) --- moto/s3/models.py | 11 +++++++---- moto/s3/responses.py | 4 +++- tests/test_s3/test_s3_multipart.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/moto/s3/models.py b/moto/s3/models.py index 4bbaa1a3f..7b40d15d0 100644 --- a/moto/s3/models.py +++ b/moto/s3/models.py @@ -330,9 +330,11 @@ class FakeKey(BaseModel): class FakeMultipart(BaseModel): - def __init__(self, key_name, metadata): + def __init__(self, key_name, metadata, storage=None, tags=None): self.key_name = key_name self.metadata = metadata + self.storage = storage + self.tags = tags self.parts = {} self.partlist = [] # ordered list of part ID's rand_b64 = base64.b64encode(os.urandom(UPLOAD_ID_BYTES)) @@ -1834,9 +1836,10 @@ class S3Backend(BaseBackend): bucket = self.get_bucket(bucket_name) return len(bucket.multiparts[multipart_id].parts) >= next_part_number_marker - def create_multipart_upload(self, bucket_name, key_name, metadata, storage_type): - multipart = FakeMultipart(key_name, metadata) - multipart.storage = storage_type + def create_multipart_upload( + self, bucket_name, key_name, metadata, storage_type, tags + ): + multipart = FakeMultipart(key_name, metadata, storage=storage_type, tags=tags) bucket = self.get_bucket(bucket_name) bucket.multiparts[multipart.id] = multipart diff --git a/moto/s3/responses.py b/moto/s3/responses.py index 39fea7f45..2ddf56368 100644 --- a/moto/s3/responses.py +++ b/moto/s3/responses.py @@ -2009,9 +2009,10 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): if body == b"" and "uploads" in query: metadata = metadata_from_headers(request.headers) + tagging = self._tagging_from_headers(request.headers) storage_type = request.headers.get("x-amz-storage-class", "STANDARD") multipart_id = self.backend.create_multipart_upload( - bucket_name, key_name, metadata, storage_type + bucket_name, key_name, metadata, storage_type, tagging ) template = self.response_template(S3_MULTIPART_INITIATE_RESPONSE) @@ -2039,6 +2040,7 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): multipart=multipart, ) key.set_metadata(multipart.metadata) + self.backend.set_key_tags(key, multipart.tags) template = self.response_template(S3_MULTIPART_COMPLETE_RESPONSE) headers = {} diff --git a/tests/test_s3/test_s3_multipart.py b/tests/test_s3/test_s3_multipart.py index 0bdf64e90..2f4468fa4 100644 --- a/tests/test_s3/test_s3_multipart.py +++ b/tests/test_s3/test_s3_multipart.py @@ -1,6 +1,7 @@ from botocore.exceptions import ClientError from moto import mock_s3 import boto3 +import os import pytest import sure # pylint: disable=unused-import @@ -52,3 +53,30 @@ def test_boto3_multipart_part_size(): err["Message"].should.equal( "The specified upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed." ) + + +@mock_s3 +def test_multipart_upload_with_tags(): + bucket = "mybucket" + key = "test/multipartuploadtag/file.txt" + tags = "a=b" + + client = boto3.client("s3", region_name="us-east-1") + client.create_bucket(Bucket=bucket) + + response = client.create_multipart_upload(Bucket=bucket, Key=key, Tagging=tags) + u = boto3.resource("s3").MultipartUpload(bucket, key, response["UploadId"]) + parts = [ + { + "ETag": u.Part(i).upload(Body=os.urandom(5 * (2 ** 20)))["ETag"], + "PartNumber": i, + } + for i in range(1, 3) + ] + + u.complete(MultipartUpload={"Parts": parts}) + + # check tags + response = client.get_object_tagging(Bucket=bucket, Key=key) + actual = {t["Key"]: t["Value"] for t in response.get("TagSet", [])} + actual.should.equal({"a": "b"})