diff --git a/moto/s3/models.py b/moto/s3/models.py index d889b8cab..fc977bb99 100644 --- a/moto/s3/models.py +++ b/moto/s3/models.py @@ -419,12 +419,31 @@ class FakeBucket(BaseModel): def set_lifecycle(self, rules): self.rules = [] for rule in rules: - # Extract actions from Lifecycle rule + # Extract and validate actions from Lifecycle rule expiration = rule.get('Expiration') transition = rule.get('Transition') - nve = rule.get('NoncurrentVersionExpiration') - nvt = rule.get('NoncurrentVersionTransition') - aimu = rule.get('AbortIncompleteMultipartUpload') + + nve_noncurrent_days = None + if rule.get('NoncurrentVersionExpiration'): + if not rule["NoncurrentVersionExpiration"].get('NoncurrentDays'): + raise MalformedXML() + nve_noncurrent_days = rule["NoncurrentVersionExpiration"]["NoncurrentDays"] + + nvt_noncurrent_days = None + nvt_storage_class = None + if rule.get('NoncurrentVersionTransition'): + if not rule["NoncurrentVersionTransition"].get('NoncurrentDays'): + raise MalformedXML() + if not rule["NoncurrentVersionTransition"].get('StorageClass'): + raise MalformedXML() + nvt_noncurrent_days = rule["NoncurrentVersionTransition"]["NoncurrentDays"] + nvt_storage_class = rule["NoncurrentVersionTransition"]["StorageClass"] + + aimu_days = None + if rule.get('AbortIncompleteMultipartUpload'): + if not rule["AbortIncompleteMultipartUpload"].get('DaysAfterInitiation'): + raise MalformedXML() + aimu_days = rule["AbortIncompleteMultipartUpload"]["DaysAfterInitiation"] eodm = None if expiration and expiration.get("ExpiredObjectDeleteMarker") is not None: @@ -472,10 +491,10 @@ class FakeBucket(BaseModel): transition_date=transition.get('Date') if transition else None, storage_class=transition.get('StorageClass') if transition else None, expired_object_delete_marker=eodm, - nve_noncurrent_days=nve.get('NoncurrentDays') if nve else None, - nvt_noncurrent_days=nvt.get('NoncurrentDays') if nvt else None, - nvt_storage_class=nvt.get('StorageClass') if nvt else None, - aimu_days=aimu.get('DaysAfterInitiation') if aimu else None, + nve_noncurrent_days=nve_noncurrent_days, + nvt_noncurrent_days=nvt_noncurrent_days, + nvt_storage_class=nvt_storage_class, + aimu_days=aimu_days, )) def delete_lifecycle(self): diff --git a/tests/test_s3/test_s3_lifecycle.py b/tests/test_s3/test_s3_lifecycle.py index 9c4bd49bf..ff89dc113 100644 --- a/tests/test_s3/test_s3_lifecycle.py +++ b/tests/test_s3/test_s3_lifecycle.py @@ -237,10 +237,10 @@ def test_lifecycle_with_nvt(): lfc = { "Rules": [ { - "NoncurrentVersionTransition": { + "NoncurrentVersionTransitions": [{ "NoncurrentDays": 30, "StorageClass": "ONEZONE_IA" - }, + }], "ID": "wholebucket", "Filter": { "Prefix": "" @@ -252,31 +252,31 @@ def test_lifecycle_with_nvt(): client.put_bucket_lifecycle_configuration(Bucket="bucket", LifecycleConfiguration=lfc) result = client.get_bucket_lifecycle_configuration(Bucket="bucket") assert len(result["Rules"]) == 1 - assert result["Rules"][0]["NoncurrentVersionTransition"]["NoncurrentDays"] == 30 - assert result["Rules"][0]["NoncurrentVersionTransition"]["StorageClass"] == "ONEZONE_IA" + assert result["Rules"][0]["NoncurrentVersionTransitions"][0]["NoncurrentDays"] == 30 + assert result["Rules"][0]["NoncurrentVersionTransitions"][0]["StorageClass"] == "ONEZONE_IA" # Change NoncurrentDays: - lfc["Rules"][0]["NoncurrentVersionTransition"]["NoncurrentDays"] = 10 + lfc["Rules"][0]["NoncurrentVersionTransitions"][0]["NoncurrentDays"] = 10 client.put_bucket_lifecycle_configuration(Bucket="bucket", LifecycleConfiguration=lfc) result = client.get_bucket_lifecycle_configuration(Bucket="bucket") assert len(result["Rules"]) == 1 - assert result["Rules"][0]["NoncurrentVersionTransition"]["NoncurrentDays"] == 10 + assert result["Rules"][0]["NoncurrentVersionTransitions"][0]["NoncurrentDays"] == 10 # Change StorageClass: - lfc["Rules"][0]["NoncurrentVersionTransition"]["StorageClass"] = "GLACIER" + lfc["Rules"][0]["NoncurrentVersionTransitions"][0]["StorageClass"] = "GLACIER" client.put_bucket_lifecycle_configuration(Bucket="bucket", LifecycleConfiguration=lfc) result = client.get_bucket_lifecycle_configuration(Bucket="bucket") assert len(result["Rules"]) == 1 - assert result["Rules"][0]["NoncurrentVersionTransition"]["StorageClass"] == "GLACIER" + assert result["Rules"][0]["NoncurrentVersionTransitions"][0]["StorageClass"] == "GLACIER" # With failures for missing children: - del lfc["Rules"][0]["NoncurrentVersionTransition"]["NoncurrentDays"] + del lfc["Rules"][0]["NoncurrentVersionTransitions"][0]["NoncurrentDays"] with assert_raises(ClientError) as err: client.put_bucket_lifecycle_configuration(Bucket="bucket", LifecycleConfiguration=lfc) assert err.exception.response["Error"]["Code"] == "MalformedXML" - lfc["Rules"][0]["NoncurrentVersionTransition"]["NoncurrentDays"] = 30 + lfc["Rules"][0]["NoncurrentVersionTransitions"][0]["NoncurrentDays"] = 30 - del lfc["Rules"][0]["NoncurrentVersionTransition"]["StorageClass"] + del lfc["Rules"][0]["NoncurrentVersionTransitions"][0]["StorageClass"] with assert_raises(ClientError) as err: client.put_bucket_lifecycle_configuration(Bucket="bucket", LifecycleConfiguration=lfc) assert err.exception.response["Error"]["Code"] == "MalformedXML"