@@ -1719,6 +1719,10 @@ def test_bucket_acceleration_configuration_exc(
1719
1719
1720
1720
1721
1721
class TestS3ObjectWritePrecondition :
1722
+ """
1723
+ https://docs.aws.amazon.com/AmazonS3/latest/userguide/conditional-writes.html
1724
+ """
1725
+
1722
1726
@pytest .fixture (autouse = True )
1723
1727
def add_snapshot_transformers (self , snapshot ):
1724
1728
snapshot .add_transformers_list (
@@ -1869,3 +1873,252 @@ def test_put_object_if_none_match_versioned_bucket(self, s3_bucket, aws_client,
1869
1873
1870
1874
list_object_versions = aws_client .s3 .list_object_versions (Bucket = s3_bucket )
1871
1875
snapshot .match ("list-object-versions" , list_object_versions )
1876
+
1877
+ @markers .aws .validated
1878
+ def test_put_object_if_match (self , s3_bucket , aws_client , snapshot ):
1879
+ key = "test-precondition"
1880
+ put_obj = aws_client .s3 .put_object (Bucket = s3_bucket , Key = key , Body = "test" )
1881
+ snapshot .match ("put-obj" , put_obj )
1882
+ etag = put_obj ["ETag" ]
1883
+
1884
+ with pytest .raises (ClientError ) as e :
1885
+ # empty object is provided
1886
+ aws_client .s3 .put_object (
1887
+ Bucket = s3_bucket , Key = key , IfMatch = "d41d8cd98f00b204e9800998ecf8427e"
1888
+ )
1889
+ snapshot .match ("put-obj-if-match-wrong-etag" , e .value .response )
1890
+
1891
+ put_obj_overwrite = aws_client .s3 .put_object (Bucket = s3_bucket , Key = key , IfMatch = etag )
1892
+ snapshot .match ("put-obj-overwrite" , put_obj_overwrite )
1893
+
1894
+ del_obj = aws_client .s3 .delete_object (Bucket = s3_bucket , Key = key )
1895
+ snapshot .match ("del-obj" , del_obj )
1896
+
1897
+ with pytest .raises (ClientError ) as e :
1898
+ aws_client .s3 .put_object (Bucket = s3_bucket , Key = key , IfMatch = etag )
1899
+ snapshot .match ("put-obj-if-match-key-not-exists" , e .value .response )
1900
+
1901
+ put_obj_after_del = aws_client .s3 .put_object (Bucket = s3_bucket , Key = key )
1902
+ snapshot .match ("put-obj-after-del" , put_obj_after_del )
1903
+
1904
+ @markers .aws .validated
1905
+ def test_put_object_if_match_validation (self , s3_bucket , aws_client , snapshot ):
1906
+ key = "test-precondition-validation"
1907
+
1908
+ with pytest .raises (ClientError ) as e :
1909
+ aws_client .s3 .put_object (Bucket = s3_bucket , Key = key , IfMatch = "*" )
1910
+ snapshot .match ("put-obj-if-match-star-value" , e .value .response )
1911
+
1912
+ with pytest .raises (ClientError ) as e :
1913
+ aws_client .s3 .put_object (Bucket = s3_bucket , Key = key , IfMatch = "abcdef" )
1914
+ snapshot .match ("put-obj-if-match-bad-value" , e .value .response )
1915
+
1916
+ with pytest .raises (ClientError ) as e :
1917
+ aws_client .s3 .put_object (Bucket = s3_bucket , Key = key , IfMatch = "bad-char_/" )
1918
+ snapshot .match ("put-obj-if-match-bad-value-2" , e .value .response )
1919
+
1920
+ @markers .aws .validated
1921
+ def test_multipart_if_match_with_put (self , s3_bucket , aws_client , snapshot ):
1922
+ key = "test-precondition"
1923
+ put_obj = aws_client .s3 .put_object (Bucket = s3_bucket , Key = key , Body = "test" )
1924
+ snapshot .match ("put-obj" , put_obj )
1925
+ put_obj_etag_1 = put_obj ["ETag" ]
1926
+
1927
+ create_multipart = aws_client .s3 .create_multipart_upload (Bucket = s3_bucket , Key = key )
1928
+ snapshot .match ("create-multipart" , create_multipart )
1929
+ upload_id = create_multipart ["UploadId" ]
1930
+
1931
+ upload_part = aws_client .s3 .upload_part (
1932
+ Bucket = s3_bucket , Key = key , UploadId = upload_id , Body = "test" , PartNumber = 1
1933
+ )
1934
+ parts = [{"ETag" : upload_part ["ETag" ], "PartNumber" : 1 }]
1935
+
1936
+ put_obj_2 = aws_client .s3 .put_object (Bucket = s3_bucket , Key = key , Body = "test2" )
1937
+ snapshot .match ("put-obj-during" , put_obj_2 )
1938
+ put_obj_etag_2 = put_obj_2 ["ETag" ]
1939
+
1940
+ with pytest .raises (ClientError ) as e :
1941
+ aws_client .s3 .complete_multipart_upload (
1942
+ Bucket = s3_bucket ,
1943
+ Key = key ,
1944
+ MultipartUpload = {"Parts" : parts },
1945
+ UploadId = upload_id ,
1946
+ IfMatch = put_obj_etag_1 ,
1947
+ )
1948
+ snapshot .match ("complete-multipart-if-match-put-before" , e .value .response )
1949
+
1950
+ # the previous PutObject request was done between the CreateMultipartUpload and completion, so it takes
1951
+ # precedence
1952
+ # you need to restart the whole multipart for it to work
1953
+ with pytest .raises (ClientError ) as e :
1954
+ aws_client .s3 .complete_multipart_upload (
1955
+ Bucket = s3_bucket ,
1956
+ Key = key ,
1957
+ MultipartUpload = {"Parts" : parts },
1958
+ UploadId = upload_id ,
1959
+ IfMatch = put_obj_etag_2 ,
1960
+ )
1961
+ snapshot .match ("complete-multipart-if-match-put-during" , e .value .response )
1962
+
1963
+ create_multipart = aws_client .s3 .create_multipart_upload (Bucket = s3_bucket , Key = key )
1964
+ snapshot .match ("create-multipart-again" , create_multipart )
1965
+ upload_id = create_multipart ["UploadId" ]
1966
+
1967
+ upload_part = aws_client .s3 .upload_part (
1968
+ Bucket = s3_bucket , Key = key , UploadId = upload_id , Body = "test" , PartNumber = 1
1969
+ )
1970
+ parts = [{"ETag" : upload_part ["ETag" ], "PartNumber" : 1 }]
1971
+
1972
+ complete_multipart = aws_client .s3 .complete_multipart_upload (
1973
+ Bucket = s3_bucket ,
1974
+ Key = key ,
1975
+ MultipartUpload = {"Parts" : parts },
1976
+ UploadId = upload_id ,
1977
+ IfMatch = put_obj_etag_2 ,
1978
+ )
1979
+ snapshot .match ("complete-multipart-if-match-put-before-restart" , complete_multipart )
1980
+
1981
+ @markers .aws .validated
1982
+ def test_multipart_if_match_with_put_identical (self , s3_bucket , aws_client , snapshot ):
1983
+ key = "test-precondition"
1984
+ put_obj = aws_client .s3 .put_object (Bucket = s3_bucket , Key = key , Body = "test" )
1985
+ snapshot .match ("put-obj" , put_obj )
1986
+ put_obj_etag_1 = put_obj ["ETag" ]
1987
+
1988
+ create_multipart = aws_client .s3 .create_multipart_upload (Bucket = s3_bucket , Key = key )
1989
+ snapshot .match ("create-multipart" , create_multipart )
1990
+ upload_id = create_multipart ["UploadId" ]
1991
+
1992
+ upload_part = aws_client .s3 .upload_part (
1993
+ Bucket = s3_bucket , Key = key , UploadId = upload_id , Body = "test" , PartNumber = 1
1994
+ )
1995
+ parts = [{"ETag" : upload_part ["ETag" ], "PartNumber" : 1 }]
1996
+
1997
+ put_obj_2 = aws_client .s3 .put_object (Bucket = s3_bucket , Key = key , Body = "test" )
1998
+ snapshot .match ("put-obj-during" , put_obj_2 )
1999
+ # same ETag as first put
2000
+ put_obj_etag_2 = put_obj_2 ["ETag" ]
2001
+ assert put_obj_etag_1 == put_obj_etag_2
2002
+
2003
+ # it seems that even if we overwrite the object with the same content, S3 will still reject the request if a
2004
+ # write operation was done between creation and completion of the multipart upload, like the `Delete`
2005
+ # counterpart of `IfNoneMatch`
2006
+
2007
+ with pytest .raises (ClientError ) as e :
2008
+ aws_client .s3 .complete_multipart_upload (
2009
+ Bucket = s3_bucket ,
2010
+ Key = key ,
2011
+ MultipartUpload = {"Parts" : parts },
2012
+ UploadId = upload_id ,
2013
+ IfMatch = put_obj_etag_2 ,
2014
+ )
2015
+ snapshot .match ("complete-multipart-if-match-put-during" , e .value .response )
2016
+ # the previous PutObject request was done between the CreateMultipartUpload and completion, so it takes
2017
+ # precedence
2018
+ # you need to restart the whole multipart for it to work
2019
+
2020
+ create_multipart = aws_client .s3 .create_multipart_upload (Bucket = s3_bucket , Key = key )
2021
+ snapshot .match ("create-multipart-again" , create_multipart )
2022
+ upload_id = create_multipart ["UploadId" ]
2023
+
2024
+ upload_part = aws_client .s3 .upload_part (
2025
+ Bucket = s3_bucket , Key = key , UploadId = upload_id , Body = "test" , PartNumber = 1
2026
+ )
2027
+ parts = [{"ETag" : upload_part ["ETag" ], "PartNumber" : 1 }]
2028
+
2029
+ complete_multipart = aws_client .s3 .complete_multipart_upload (
2030
+ Bucket = s3_bucket ,
2031
+ Key = key ,
2032
+ MultipartUpload = {"Parts" : parts },
2033
+ UploadId = upload_id ,
2034
+ IfMatch = put_obj_etag_2 ,
2035
+ )
2036
+ snapshot .match ("complete-multipart-if-match-put-before-restart" , complete_multipart )
2037
+
2038
+ @markers .aws .validated
2039
+ def test_multipart_if_match_with_delete (self , s3_bucket , aws_client , snapshot ):
2040
+ key = "test-precondition"
2041
+ put_obj = aws_client .s3 .put_object (Bucket = s3_bucket , Key = key , Body = "test" )
2042
+ snapshot .match ("put-obj" , put_obj )
2043
+ obj_etag = put_obj ["ETag" ]
2044
+
2045
+ create_multipart = aws_client .s3 .create_multipart_upload (Bucket = s3_bucket , Key = key )
2046
+ snapshot .match ("create-multipart" , create_multipart )
2047
+ upload_id = create_multipart ["UploadId" ]
2048
+
2049
+ upload_part = aws_client .s3 .upload_part (
2050
+ Bucket = s3_bucket , Key = key , UploadId = upload_id , Body = "test" , PartNumber = 1
2051
+ )
2052
+ parts = [{"ETag" : upload_part ["ETag" ], "PartNumber" : 1 }]
2053
+
2054
+ del_obj = aws_client .s3 .delete_object (Bucket = s3_bucket , Key = key )
2055
+ snapshot .match ("del-obj" , del_obj )
2056
+
2057
+ with pytest .raises (ClientError ) as e :
2058
+ aws_client .s3 .complete_multipart_upload (
2059
+ Bucket = s3_bucket ,
2060
+ Key = key ,
2061
+ MultipartUpload = {"Parts" : parts },
2062
+ UploadId = upload_id ,
2063
+ IfMatch = obj_etag ,
2064
+ )
2065
+ snapshot .match ("complete-multipart-after-del" , e .value .response )
2066
+
2067
+ put_obj_2 = aws_client .s3 .put_object (Bucket = s3_bucket , Key = key , Body = "test" )
2068
+ snapshot .match ("put-obj-2" , put_obj_2 )
2069
+ obj_etag_2 = put_obj_2 ["ETag" ]
2070
+
2071
+ with pytest .raises (ClientError ) as e :
2072
+ # even if we recreated the object, it still fails as it was done after the start of the upload
2073
+ aws_client .s3 .complete_multipart_upload (
2074
+ Bucket = s3_bucket ,
2075
+ Key = key ,
2076
+ MultipartUpload = {"Parts" : parts },
2077
+ UploadId = upload_id ,
2078
+ IfMatch = obj_etag_2 ,
2079
+ )
2080
+ snapshot .match ("complete-multipart-if-match-after-put" , e .value .response )
2081
+
2082
+ @markers .aws .validated
2083
+ def test_put_object_if_match_versioned_bucket (self , s3_bucket , aws_client , snapshot ):
2084
+ aws_client .s3 .put_bucket_versioning (
2085
+ Bucket = s3_bucket , VersioningConfiguration = {"Status" : "Enabled" }
2086
+ )
2087
+ key = "test-precondition"
2088
+ put_obj = aws_client .s3 .put_object (Bucket = s3_bucket , Key = key , Body = "test" )
2089
+ snapshot .match ("put-obj" , put_obj )
2090
+ put_obj_etag_1 = put_obj ["ETag" ]
2091
+
2092
+ with pytest .raises (ClientError ) as e :
2093
+ aws_client .s3 .put_object (Bucket = s3_bucket , Key = key , IfMatch = "abcdef" )
2094
+ snapshot .match ("put-obj-if-none-match-bad-value" , e .value .response )
2095
+
2096
+ del_obj = aws_client .s3 .delete_object (Bucket = s3_bucket , Key = key )
2097
+ snapshot .match ("del-obj" , del_obj )
2098
+
2099
+ # if the last object is a delete marker, then we can't use IfMatch
2100
+ with pytest .raises (ClientError ) as e :
2101
+ aws_client .s3 .put_object (Bucket = s3_bucket , Key = key , IfMatch = put_obj_etag_1 )
2102
+ snapshot .match ("put-obj-after-del-exc" , e .value .response )
2103
+
2104
+ put_obj_2 = aws_client .s3 .put_object (Bucket = s3_bucket , Key = key , Body = "test-after-del" )
2105
+ snapshot .match ("put-obj-after-del" , put_obj_2 )
2106
+ put_obj_etag_2 = put_obj_2 ["ETag" ]
2107
+
2108
+ put_obj_3 = aws_client .s3 .put_object (
2109
+ Bucket = s3_bucket , Key = key , Body = "test-if-match" , IfMatch = put_obj_etag_2
2110
+ )
2111
+ snapshot .match ("put-obj-if-match" , put_obj_3 )
2112
+
2113
+ list_object_versions = aws_client .s3 .list_object_versions (Bucket = s3_bucket )
2114
+ snapshot .match ("list-object-versions" , list_object_versions )
2115
+
2116
+ @markers .aws .validated
2117
+ def test_put_object_if_match_and_if_none_match_validation (
2118
+ self , s3_bucket , aws_client , snapshot
2119
+ ):
2120
+ key = "test-precondition-validation"
2121
+
2122
+ with pytest .raises (ClientError ) as e :
2123
+ aws_client .s3 .put_object (Bucket = s3_bucket , Key = key , IfNoneMatch = "*" , IfMatch = "abcdef" )
2124
+ snapshot .match ("put-obj-both-precondition" , e .value .response )
0 commit comments