Skip to content

complete_multipart_upload with IfNoneMatch="*" fails to detect existing objects created via multipart upload #9028

@JayThomason

Description

@JayThomason

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions