-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
Description
The IfNoneMatch="*" parameter in complete_multipart_upload only works correctly when checking against objects that were created via put_object. It fails to detect existing objects that were created via previous complete_multipart_upload calls, incorrectly allowing the
upload to succeed when it should fail with PreconditionFailed.
Expected Behavior
When calling complete_multipart_upload with IfNoneMatch="*", it should fail with PreconditionFailed if any object exists at that key, regardless of how the object was originally created (via put_object or complete_multipart_upload).
Actual Behavior
• ✅ Works correctly: If existing object was created via put_object, then complete_multipart_upload with IfNoneMatch="" correctly fails
• ❌ Bug: If existing object was created via complete_multipart_upload, then subsequent complete_multipart_upload with IfNoneMatch="" incorrectly succeeds
Reproduction Code
import boto3
from moto import mock_aws
from botocore.exceptions import ClientError
from io import BytesIO
S3_UPLOAD_PART_MIN_SIZE = 5 * 1024 * 1024 # 5MB
@mock_aws
def test_moto_bug():
client = boto3.client("s3", region_name="us-east-1")
bucket_name = "test-bucket"
key = "test-key"
client.create_bucket(Bucket=bucket_name)
# Step 1: Create object via multipart upload
part1 = b"a" * S3_UPLOAD_PART_MIN_SIZE
multipart1 = client.create_multipart_upload(Bucket=bucket_name, Key=key)
kwargs1 = {"Bucket": bucket_name, "Key": key, "UploadId": multipart1["UploadId"]}
up1 = client.upload_part(Body=BytesIO(part1), PartNumber=1, **kwargs1)
parts1 = {"Parts": [{"ETag": up1["ETag"], "PartNumber": 1}]}
# Complete first multipart upload (object now exists)
client.complete_multipart_upload(MultipartUpload=parts1, **kwargs1)
# Step 2: Try second multipart upload with IfNoneMatch="*" (should fail but doesn't)
part2 = b"b" * S3_UPLOAD_PART_MIN_SIZE
multipart2 = client.create_multipart_upload(Bucket=bucket_name, Key=key)
kwargs2 = {"Bucket": bucket_name, "Key": key, "UploadId": multipart2["UploadId"]}
up2 = client.upload_part(Body=BytesIO(part2), PartNumber=1, **kwargs2)
parts2 = {"Parts": [{"ETag": up2["ETag"], "PartNumber": 1}]}
# This should raise ClientError with PreconditionFailed, but it doesn't
try:
result = client.complete_multipart_upload(
MultipartUpload=parts2, IfNoneMatch="*", **kwargs2
)
print(f"BUG: Should have failed but got HTTP {result['ResponseMetadata']['HTTPStatusCode']}")
except ClientError as exc:
print(f"Expected behavior: {exc.response['Error']}")
if __name__ == "__main__":
test_moto_bug()Output when using S3 directly without moto:
Expected behavior: {'Code': 'PreconditionFailed', 'Message': 'At least one of the pre-conditions you specified did not hold', 'Condition': 'If-None-Match'}
Output when using moto:
BUG: Should have failed but got HTTP 200
Working Case (for comparison)
The following code works correctly because the initial object is created via put_object:
# Step 1: Create object via put_object first
client.put_object(Bucket=bucket_name, Key="test-key", Body=b"existing")
# Then multipart upload with IfNoneMatch="*" correctly fails
# ... (same multipart upload code as above)Environment
- macos sequoia 15.5
- python 3.12.7
• moto version: 5.1.6
• boto3 version: 1.39.0
• botocore version: 1.38.27
Related
This appears to be related to PR #8380 which added IfNoneMatch support to complete_multipart_upload.