Skip to content

Commit 30249e8

Browse files
authored
fix ObjectTagging with DeleteMarker (#11185)
1 parent e2d6730 commit 30249e8

File tree

5 files changed

+126
-38
lines changed

5 files changed

+126
-38
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ def get_object(
171171
see: https://docs.aws.amazon.com/AmazonS3/latest/userguide/DeleteMarker.html
172172
:param raise_for_delete_marker: optional, indicates if the method should raise an exception if the found object
173173
is a S3DeleteMarker. If False, it can return a S3DeleteMarker
174+
TODO: we need to remove the `raise_for_delete_marker` parameter and replace it with the error type to raise
175+
(MethodNotAllowed or NoSuchKey)
174176
:return:
175177
:raises NoSuchKey if the object key does not exist at all, or if the object is a DeleteMarker
176178
:raises MethodNotAllowed if the object is a DeleteMarker and the operation is not allowed against it

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

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2645,11 +2645,7 @@ def put_object_tagging(
26452645
) -> PutObjectTaggingOutput:
26462646
store, s3_bucket = self._get_cross_account_bucket(context, bucket)
26472647

2648-
s3_object = s3_bucket.get_object(
2649-
key=key,
2650-
version_id=version_id,
2651-
raise_for_delete_marker=False, # We can tag DeleteMarker
2652-
)
2648+
s3_object = s3_bucket.get_object(key=key, version_id=version_id, http_method="PUT")
26532649

26542650
if "TagSet" not in tagging:
26552651
raise MalformedXML()
@@ -2681,12 +2677,23 @@ def get_object_tagging(
26812677
store, s3_bucket = self._get_cross_account_bucket(context, bucket)
26822678

26832679
try:
2684-
s3_object = s3_bucket.get_object(
2685-
key=key,
2686-
version_id=version_id,
2687-
raise_for_delete_marker=False, # We can tag DeleteMarker
2688-
)
2680+
s3_object = s3_bucket.get_object(key=key, version_id=version_id)
26892681
except NoSuchKey as e:
2682+
# TODO: remove the hack under and update the S3Bucket model before the next major version, as it might break
2683+
# persistence: we need to remove the `raise_for_delete_marker` parameter and replace it with the error type
2684+
# to raise (MethodNotAllowed or NoSuchKey)
2685+
if s3_bucket.versioning_status and (
2686+
s3_object_version := s3_bucket.objects.get(key, version_id)
2687+
):
2688+
raise MethodNotAllowed(
2689+
"The specified method is not allowed against this resource.",
2690+
Method="GET",
2691+
ResourceType="DeleteMarker",
2692+
DeleteMarker=True,
2693+
Allow="DELETE",
2694+
VersionId=s3_object_version.version_id,
2695+
)
2696+
26902697
# There a weird AWS validated bug in S3: the returned key contains the bucket name as well
26912698
# follow AWS on this one
26922699
e.Key = f"{bucket}/{key}"
@@ -2712,11 +2719,7 @@ def delete_object_tagging(
27122719
) -> DeleteObjectTaggingOutput:
27132720
store, s3_bucket = self._get_cross_account_bucket(context, bucket)
27142721

2715-
s3_object = s3_bucket.get_object(
2716-
key=key,
2717-
version_id=version_id,
2718-
raise_for_delete_marker=False,
2719-
)
2722+
s3_object = s3_bucket.get_object(key=key, version_id=version_id, http_method="DELETE")
27202723

27212724
store.TAGS.tags.pop(get_unique_key_id(bucket, key, version_id), None)
27222725
response = DeleteObjectTaggingOutput()

tests/aws/services/s3/test_s3_api.py

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,17 +1171,48 @@ def test_object_tagging_versioned(self, s3_bucket, aws_client, snapshot):
11711171
# Put a DeleteMarker on top of the stack
11721172
delete_current = aws_client.s3.delete_object(Bucket=s3_bucket, Key=object_key)
11731173
snapshot.match("put-delete-marker", delete_current)
1174+
version_id_delete_marker = delete_current["VersionId"]
11741175

1175-
# test to put/get tagging on a DeleteMarker
1176-
put_bucket_tags = aws_client.s3.put_object_tagging(
1177-
Bucket=s3_bucket, Key=object_key, VersionId=version_id_1, Tagging=tag_set_2
1178-
)
1179-
snapshot.match("put-object-tags-delete-marker", put_bucket_tags)
1176+
# test to put/get tagging on the DeleteMarker
1177+
with pytest.raises(ClientError) as e:
1178+
aws_client.s3.put_object_tagging(
1179+
Bucket=s3_bucket,
1180+
Key=object_key,
1181+
VersionId=version_id_delete_marker,
1182+
Tagging=tag_set_2,
1183+
)
1184+
snapshot.match("put-object-tags-delete-marker-id", e.value.response)
11801185

1181-
get_bucket_tags = aws_client.s3.get_object_tagging(
1182-
Bucket=s3_bucket, Key=object_key, VersionId=version_id_1
1183-
)
1184-
snapshot.match("get-object-tags-delete-marker", get_bucket_tags)
1186+
with pytest.raises(ClientError) as e:
1187+
aws_client.s3.get_object_tagging(
1188+
Bucket=s3_bucket, Key=object_key, VersionId=version_id_delete_marker
1189+
)
1190+
snapshot.match("get-object-tags-delete-marker-id", e.value.response)
1191+
1192+
with pytest.raises(ClientError) as e:
1193+
aws_client.s3.delete_object_tagging(
1194+
Bucket=s3_bucket, Key=object_key, VersionId=version_id_delete_marker
1195+
)
1196+
snapshot.match("delete-object-tags-delete-marker-id", e.value.response)
1197+
1198+
# test to put/get tagging on latest version (DeleteMarker)
1199+
with pytest.raises(ClientError) as e:
1200+
aws_client.s3.put_object_tagging(Bucket=s3_bucket, Key=object_key, Tagging=tag_set_2)
1201+
snapshot.match("put-object-tags-delete-marker-latest", e.value.response)
1202+
1203+
with pytest.raises(ClientError) as e:
1204+
aws_client.s3.get_object_tagging(
1205+
Bucket=s3_bucket,
1206+
Key=object_key,
1207+
)
1208+
snapshot.match("get-object-tags-delete-marker-latest", e.value.response)
1209+
1210+
with pytest.raises(ClientError) as e:
1211+
aws_client.s3.delete_object_tagging(
1212+
Bucket=s3_bucket,
1213+
Key=object_key,
1214+
)
1215+
snapshot.match("delete-object-tags-delete-marker-latest", e.value.response)
11851216

11861217
@markers.aws.validated
11871218
def test_put_object_with_tags(self, s3_bucket, aws_client, snapshot):

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

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1837,7 +1837,7 @@
18371837
}
18381838
},
18391839
"tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tagging_versioned": {
1840-
"recorded-date": "20-11-2023, 17:25:12",
1840+
"recorded-date": "11-07-2024, 13:53:37",
18411841
"recorded-content": {
18421842
"put-obj-0": {
18431843
"ETag": "\"86639701cdcc5b39438a5f009bd74cb1\"",
@@ -1918,24 +1918,76 @@
19181918
"HTTPStatusCode": 204
19191919
}
19201920
},
1921-
"put-object-tags-delete-marker": {
1922-
"VersionId": "<version-id:1>",
1921+
"put-object-tags-delete-marker-id": {
1922+
"Error": {
1923+
"Code": "MethodNotAllowed",
1924+
"Message": "The specified method is not allowed against this resource.",
1925+
"Method": "PUT",
1926+
"ResourceType": "DeleteMarker"
1927+
},
19231928
"ResponseMetadata": {
19241929
"HTTPHeaders": {},
1925-
"HTTPStatusCode": 200
1930+
"HTTPStatusCode": 405
19261931
}
19271932
},
1928-
"get-object-tags-delete-marker": {
1929-
"TagSet": [
1930-
{
1931-
"Key": "tag1",
1932-
"Value": "tag1"
1933-
}
1934-
],
1935-
"VersionId": "<version-id:1>",
1933+
"get-object-tags-delete-marker-id": {
1934+
"Error": {
1935+
"Code": "MethodNotAllowed",
1936+
"Message": "The specified method is not allowed against this resource.",
1937+
"Method": "GET",
1938+
"ResourceType": "DeleteMarker"
1939+
},
19361940
"ResponseMetadata": {
19371941
"HTTPHeaders": {},
1938-
"HTTPStatusCode": 200
1942+
"HTTPStatusCode": 405
1943+
}
1944+
},
1945+
"delete-object-tags-delete-marker-id": {
1946+
"Error": {
1947+
"Code": "MethodNotAllowed",
1948+
"Message": "The specified method is not allowed against this resource.",
1949+
"Method": "DELETE",
1950+
"ResourceType": "DeleteMarker"
1951+
},
1952+
"ResponseMetadata": {
1953+
"HTTPHeaders": {},
1954+
"HTTPStatusCode": 405
1955+
}
1956+
},
1957+
"put-object-tags-delete-marker-latest": {
1958+
"Error": {
1959+
"Code": "MethodNotAllowed",
1960+
"Message": "The specified method is not allowed against this resource.",
1961+
"Method": "PUT",
1962+
"ResourceType": "DeleteMarker"
1963+
},
1964+
"ResponseMetadata": {
1965+
"HTTPHeaders": {},
1966+
"HTTPStatusCode": 405
1967+
}
1968+
},
1969+
"get-object-tags-delete-marker-latest": {
1970+
"Error": {
1971+
"Code": "MethodNotAllowed",
1972+
"Message": "The specified method is not allowed against this resource.",
1973+
"Method": "GET",
1974+
"ResourceType": "DeleteMarker"
1975+
},
1976+
"ResponseMetadata": {
1977+
"HTTPHeaders": {},
1978+
"HTTPStatusCode": 405
1979+
}
1980+
},
1981+
"delete-object-tags-delete-marker-latest": {
1982+
"Error": {
1983+
"Code": "MethodNotAllowed",
1984+
"Message": "The specified method is not allowed against this resource.",
1985+
"Method": "DELETE",
1986+
"ResourceType": "DeleteMarker"
1987+
},
1988+
"ResponseMetadata": {
1989+
"HTTPHeaders": {},
1990+
"HTTPStatusCode": 405
19391991
}
19401992
}
19411993
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"last_validated_date": "2023-08-02T22:04:47+00:00"
4040
},
4141
"tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tagging_versioned": {
42-
"last_validated_date": "2023-11-20T16:25:12+00:00"
42+
"last_validated_date": "2024-07-11T13:53:37+00:00"
4343
},
4444
"tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tags_delete_or_overwrite_object": {
4545
"last_validated_date": "2023-08-02T21:52:10+00:00"

0 commit comments

Comments
 (0)