Skip to content

Commit 62f162f

Browse files
authored
S3: add error for DeleteObject preconditions for regular buckets (#12787)
1 parent fec3c9a commit 62f162f

File tree

4 files changed

+166
-0
lines changed

4 files changed

+166
-0
lines changed

localstack-core/localstack/services/s3/provider.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,24 @@ def delete_object(
11951195
ArgumentName="x-amz-bypass-governance-retention",
11961196
)
11971197

1198+
# TODO: this is only supported for Directory Buckets
1199+
non_supported_precondition = None
1200+
if if_match:
1201+
non_supported_precondition = "If-Match"
1202+
if if_match_size:
1203+
non_supported_precondition = "x-amz-if-match-size"
1204+
if if_match_last_modified_time:
1205+
non_supported_precondition = "x-amz-if-match-last-modified-time"
1206+
if non_supported_precondition:
1207+
LOG.warning(
1208+
"DeleteObject Preconditions is only supported for Directory Buckets. "
1209+
"LocalStack does not support Directory Buckets yet."
1210+
)
1211+
raise NotImplementedException(
1212+
"A header you provided implies functionality that is not implemented",
1213+
Header=non_supported_precondition,
1214+
)
1215+
11981216
if s3_bucket.versioning_status is None:
11991217
if version_id and version_id != "null":
12001218
raise InvalidArgument(

tests/aws/services/s3/test_s3_api.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import datetime
12
import json
23
import string
34
from operator import itemgetter
@@ -2341,3 +2342,50 @@ def test_delete_metrics_configuration_twice(self, s3_bucket, aws_client, snapsho
23412342
with pytest.raises(ClientError) as delete_err:
23422343
aws_client.s3.delete_bucket_metrics_configuration(Bucket=s3_bucket, Id=metric_id)
23432344
snapshot.match("delete_bucket_metrics_configuration_2", delete_err.value.response)
2345+
2346+
2347+
class TestS3DeletePrecondition:
2348+
@markers.aws.validated
2349+
def test_delete_object_if_match_non_express(self, s3_bucket, aws_client, snapshot):
2350+
key = "test-precondition"
2351+
aws_client.s3.put_object(Bucket=s3_bucket, Key=key)
2352+
2353+
with pytest.raises(ClientError) as e:
2354+
aws_client.s3.delete_object(Bucket=s3_bucket, Key=key, IfMatch="badvalue")
2355+
snapshot.match("delete-obj-if-match", e.value.response)
2356+
2357+
@markers.aws.validated
2358+
def test_delete_object_if_match_modified_non_express(self, s3_bucket, aws_client, snapshot):
2359+
key = "test-precondition"
2360+
aws_client.s3.put_object(Bucket=s3_bucket, Key=key)
2361+
2362+
earlier = datetime.datetime.now(tz=datetime.UTC) - datetime.timedelta(days=1)
2363+
2364+
with pytest.raises(ClientError) as e:
2365+
aws_client.s3.delete_object(Bucket=s3_bucket, Key=key, IfMatchLastModifiedTime=earlier)
2366+
snapshot.match("delete-obj-if-match-last-modified", e.value.response)
2367+
2368+
@markers.aws.validated
2369+
def test_delete_object_if_match_size_non_express(self, s3_bucket, aws_client, snapshot):
2370+
key = "test-precondition"
2371+
aws_client.s3.put_object(Bucket=s3_bucket, Key=key)
2372+
2373+
with pytest.raises(ClientError) as e:
2374+
aws_client.s3.delete_object(Bucket=s3_bucket, Key=key, IfMatchSize=10)
2375+
snapshot.match("delete-obj-if-match-size", e.value.response)
2376+
2377+
@markers.aws.validated
2378+
def test_delete_object_if_match_all_non_express(self, s3_bucket, aws_client, snapshot):
2379+
key = "test-precondition"
2380+
aws_client.s3.put_object(Bucket=s3_bucket, Key=key)
2381+
earlier = datetime.datetime.now(tz=datetime.UTC) - datetime.timedelta(days=1)
2382+
2383+
with pytest.raises(ClientError) as e:
2384+
aws_client.s3.delete_object(
2385+
Bucket=s3_bucket,
2386+
Key=key,
2387+
IfMatchSize=10,
2388+
IfMatch="badvalue",
2389+
IfMatchLastModifiedTime=earlier,
2390+
)
2391+
snapshot.match("delete-obj-if-match-all", e.value.response)

tests/aws/services/s3/test_s3_api.snapshot.json

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4601,5 +4601,69 @@
46014601
}
46024602
}
46034603
}
4604+
},
4605+
"tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_non_express": {
4606+
"recorded-date": "23-06-2025, 08:53:20",
4607+
"recorded-content": {
4608+
"delete-obj-if-match": {
4609+
"Error": {
4610+
"Code": "NotImplemented",
4611+
"Header": "If-Match",
4612+
"Message": "A header you provided implies functionality that is not implemented"
4613+
},
4614+
"ResponseMetadata": {
4615+
"HTTPHeaders": {},
4616+
"HTTPStatusCode": 501
4617+
}
4618+
}
4619+
}
4620+
},
4621+
"tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_modified_non_express": {
4622+
"recorded-date": "23-06-2025, 09:05:52",
4623+
"recorded-content": {
4624+
"delete-obj-if-match-last-modified": {
4625+
"Error": {
4626+
"Code": "NotImplemented",
4627+
"Header": "x-amz-if-match-last-modified-time",
4628+
"Message": "A header you provided implies functionality that is not implemented"
4629+
},
4630+
"ResponseMetadata": {
4631+
"HTTPHeaders": {},
4632+
"HTTPStatusCode": 501
4633+
}
4634+
}
4635+
}
4636+
},
4637+
"tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_size_non_express": {
4638+
"recorded-date": "23-06-2025, 09:05:59",
4639+
"recorded-content": {
4640+
"delete-obj-if-match-size": {
4641+
"Error": {
4642+
"Code": "NotImplemented",
4643+
"Header": "x-amz-if-match-size",
4644+
"Message": "A header you provided implies functionality that is not implemented"
4645+
},
4646+
"ResponseMetadata": {
4647+
"HTTPHeaders": {},
4648+
"HTTPStatusCode": 501
4649+
}
4650+
}
4651+
}
4652+
},
4653+
"tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_all_non_express": {
4654+
"recorded-date": "23-06-2025, 09:06:41",
4655+
"recorded-content": {
4656+
"delete-obj-if-match-all": {
4657+
"Error": {
4658+
"Code": "NotImplemented",
4659+
"Header": "x-amz-if-match-last-modified-time",
4660+
"Message": "A header you provided implies functionality that is not implemented"
4661+
},
4662+
"ResponseMetadata": {
4663+
"HTTPHeaders": {},
4664+
"HTTPStatusCode": 501
4665+
}
4666+
}
4667+
}
46044668
}
46054669
}

tests/aws/services/s3/test_s3_api.validation.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,42 @@
6868
"tests/aws/services/s3/test_s3_api.py::TestS3BucketVersioning::test_object_version_id_format": {
6969
"last_validated_date": "2025-01-21T18:10:31+00:00"
7070
},
71+
"tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_all_non_express": {
72+
"last_validated_date": "2025-06-23T09:06:43+00:00",
73+
"durations_in_seconds": {
74+
"setup": 0.96,
75+
"call": 0.24,
76+
"teardown": 1.13,
77+
"total": 2.33
78+
}
79+
},
80+
"tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_modified_non_express": {
81+
"last_validated_date": "2025-06-23T09:05:53+00:00",
82+
"durations_in_seconds": {
83+
"setup": 1.05,
84+
"call": 0.23,
85+
"teardown": 1.16,
86+
"total": 2.44
87+
}
88+
},
89+
"tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_non_express": {
90+
"last_validated_date": "2025-06-23T08:53:21+00:00",
91+
"durations_in_seconds": {
92+
"setup": 1.05,
93+
"call": 0.24,
94+
"teardown": 1.1,
95+
"total": 2.39
96+
}
97+
},
98+
"tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_size_non_express": {
99+
"last_validated_date": "2025-06-23T09:06:00+00:00",
100+
"durations_in_seconds": {
101+
"setup": 0.98,
102+
"call": 0.22,
103+
"teardown": 1.17,
104+
"total": 2.37
105+
}
106+
},
71107
"tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_delete_metrics_configuration": {
72108
"last_validated_date": "2025-06-13T08:33:19+00:00"
73109
},

0 commit comments

Comments
 (0)