Add basics for S3 bucket lifecycles.

This commit is contained in:
Steve Pulec 2015-06-02 23:11:23 -04:00
parent 72dbd349ac
commit 91fffbb83b
2 changed files with 95 additions and 3 deletions

View File

@ -161,6 +161,20 @@ class FakeMultipart(object):
yield self.parts[part_id]
class LifecycleRule(object):
def __init__(self, id=None, prefix=None, status=None, expiration_days=None,
expiration_date=None, transition_days=None,
transition_date=None, storage_class=None):
self.id = id
self.prefix = prefix
self.status = status
self.expiration_days = expiration_days
self.expiration_date = expiration_date
self.transition_days = transition_days
self.transition_date = transition_date
self.storage_class = storage_class
class FakeBucket(object):
def __init__(self, name, region_name):
@ -169,6 +183,7 @@ class FakeBucket(object):
self.keys = _VersionedKeyStore()
self.multiparts = {}
self.versioning_status = None
self.rules = []
@property
def location(self):
@ -178,6 +193,25 @@ class FakeBucket(object):
def is_versioned(self):
return self.versioning_status == 'Enabled'
def set_lifecycle(self, rules):
self.rules = []
for rule in rules:
expiration = rule.get('Expiration')
transition = rule.get('Transition')
self.rules.append(LifecycleRule(
id=rule.get('ID'),
prefix=rule['Prefix'],
status=rule['Status'],
expiration_days=expiration.get('Days') if expiration else None,
expiration_date=expiration.get('Date') if expiration else None,
transition_days=transition.get('Days') if transition else None,
transition_date=transition.get('Date') if transition else None,
storage_class=transition['StorageClass'] if transition else None,
))
def delete_lifecycle(self):
self.rules = []
def get_cfn_attribute(self, attribute_name):
from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
if attribute_name == 'DomainName':
@ -235,6 +269,10 @@ class S3Backend(BaseBackend):
return itertools.chain(*(l for _, l in bucket.keys.iterlists()))
def set_bucket_lifecycle(self, bucket_name, rules):
bucket = self.get_bucket(bucket_name)
bucket.set_lifecycle(rules)
def set_key(self, bucket_name, key_name, value, storage=None, etag=None):
key_name = clean_key_name(key_name)

View File

@ -4,6 +4,7 @@ import re
import six
from six.moves.urllib.parse import parse_qs, urlparse
import xmltodict
from moto.core.responses import _TemplateEnvironmentMixin
@ -65,7 +66,7 @@ class ResponseObject(_TemplateEnvironmentMixin):
elif method == 'PUT':
return self._bucket_response_put(request, region_name, bucket_name, querystring, headers)
elif method == 'DELETE':
return self._bucket_response_delete(bucket_name, headers)
return self._bucket_response_delete(bucket_name, querystring, headers)
elif method == 'POST':
return self._bucket_response_post(request, bucket_name, headers)
else:
@ -92,6 +93,12 @@ class ResponseObject(_TemplateEnvironmentMixin):
bucket = self.backend.get_bucket(bucket_name)
template = self.response_template(S3_BUCKET_LOCATION)
return 200, headers, template.render(location=bucket.location)
elif 'lifecycle' in querystring:
bucket = self.backend.get_bucket(bucket_name)
if not bucket.rules:
return 404, headers, "NoSuchLifecycleConfiguration"
template = self.response_template(S3_BUCKET_LIFECYCLE_CONFIGURATION)
return 200, headers, template.render(rules=bucket.rules)
elif 'versioning' in querystring:
versioning = self.backend.get_bucket_versioning(bucket_name)
template = self.response_template(S3_BUCKET_GET_VERSIONING)
@ -143,14 +150,23 @@ class ResponseObject(_TemplateEnvironmentMixin):
else:
# Flask server
body = request.data
body = body.decode('utf-8')
if 'versioning' in querystring:
ver = re.search('<Status>([A-Za-z]+)</Status>', body.decode('utf-8'))
ver = re.search('<Status>([A-Za-z]+)</Status>', body)
if ver:
self.backend.set_bucket_versioning(bucket_name, ver.group(1))
template = self.response_template(S3_BUCKET_VERSIONING)
return template.render(bucket_versioning_status=ver.group(1))
else:
return 404, headers, ""
elif 'lifecycle' in querystring:
rules = xmltodict.parse(body)['LifecycleConfiguration']['Rule']
if not isinstance(rules, list):
# If there is only one rule, xmldict returns just the item
rules = [rules]
self.backend.set_bucket_lifecycle(bucket_name, rules)
return ""
else:
try:
new_bucket = self.backend.create_bucket(bucket_name, region_name)
@ -163,7 +179,12 @@ class ResponseObject(_TemplateEnvironmentMixin):
template = self.response_template(S3_BUCKET_CREATE_RESPONSE)
return 200, headers, template.render(bucket=new_bucket)
def _bucket_response_delete(self, bucket_name, headers):
def _bucket_response_delete(self, bucket_name, querystring, headers):
if 'lifecycle' in querystring:
bucket = self.backend.get_bucket(bucket_name)
bucket.delete_lifecycle()
return 204, headers, ""
removed_bucket = self.backend.delete_bucket(bucket_name)
if removed_bucket:
@ -497,6 +518,39 @@ S3_DELETE_BUCKET_WITH_ITEMS_ERROR = """<?xml version="1.0" encoding="UTF-8"?>
S3_BUCKET_LOCATION = """<?xml version="1.0" encoding="UTF-8"?>
<LocationConstraint xmlns="http://s3.amazonaws.com/doc/2006-03-01/">{{ location }}</LocationConstraint>"""
S3_BUCKET_LIFECYCLE_CONFIGURATION = """<?xml version="1.0" encoding="UTF-8"?>
<LifecycleConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
{% for rule in rules %}
<Rule>
<ID>{{ rule.id }}</ID>
<Prefix>{{ rule.prefix if rule.prefix != None }}</Prefix>
<Status>{{ rule.status }}</Status>
{% if rule.storage_class %}
<Transition>
{% if rule.transition_days %}
<Days>{{ rule.transition_days }}</Days>
{% endif %}
{% if rule.transition_date %}
<Date>{{ rule.transition_date }}</Date>
{% endif %}
<StorageClass>{{ rule.storage_class }}</StorageClass>
</Transition>
{% endif %}
{% if rule.expiration_days or rule.expiration_date %}
<Expiration>
{% if rule.expiration_days %}
<Days>{{ rule.expiration_days }}</Days>
{% endif %}
{% if rule.expiration_date %}
<Date>{{ rule.expiration_date }}</Date>
{% endif %}
</Expiration>
{% endif %}
</Rule>
{% endfor %}
</LifecycleConfiguration>
"""
S3_BUCKET_VERSIONING = """
<?xml version="1.0" encoding="UTF-8"?>
<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">