Cleanup S3 model methods to better deal with missing buckets.
This commit is contained in:
parent
be25a2ba99
commit
5e35348c0d
@ -1,2 +1,6 @@
|
|||||||
class BucketAlreadyExists(Exception):
|
class BucketAlreadyExists(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MissingBucket(Exception):
|
||||||
|
pass
|
||||||
|
@ -7,7 +7,7 @@ import itertools
|
|||||||
|
|
||||||
from moto.core import BaseBackend
|
from moto.core import BaseBackend
|
||||||
from moto.core.utils import iso_8601_datetime, rfc_1123_datetime
|
from moto.core.utils import iso_8601_datetime, rfc_1123_datetime
|
||||||
from .exceptions import BucketAlreadyExists
|
from .exceptions import BucketAlreadyExists, MissingBucket
|
||||||
from .utils import clean_key_name, _VersionedKeyStore
|
from .utils import clean_key_name, _VersionedKeyStore
|
||||||
|
|
||||||
UPLOAD_ID_BYTES = 43
|
UPLOAD_ID_BYTES = 43
|
||||||
@ -177,40 +177,42 @@ class S3Backend(BaseBackend):
|
|||||||
return self.buckets.values()
|
return self.buckets.values()
|
||||||
|
|
||||||
def get_bucket(self, bucket_name):
|
def get_bucket(self, bucket_name):
|
||||||
return self.buckets.get(bucket_name)
|
try:
|
||||||
|
return self.buckets[bucket_name]
|
||||||
|
except KeyError:
|
||||||
|
raise MissingBucket()
|
||||||
|
|
||||||
def delete_bucket(self, bucket_name):
|
def delete_bucket(self, bucket_name):
|
||||||
bucket = self.buckets.get(bucket_name)
|
bucket = self.get_bucket(bucket_name)
|
||||||
if bucket:
|
if bucket.keys:
|
||||||
if bucket.keys:
|
# Can't delete a bucket with keys
|
||||||
# Can't delete a bucket with keys
|
return False
|
||||||
return False
|
else:
|
||||||
else:
|
return self.buckets.pop(bucket_name)
|
||||||
return self.buckets.pop(bucket_name)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def set_bucket_versioning(self, bucket_name, status):
|
def set_bucket_versioning(self, bucket_name, status):
|
||||||
self.buckets[bucket_name].versioning_status = status
|
self.get_bucket(bucket_name).versioning_status = status
|
||||||
|
|
||||||
def get_bucket_versioning(self, bucket_name):
|
def get_bucket_versioning(self, bucket_name):
|
||||||
return self.buckets[bucket_name].versioning_status
|
return self.get_bucket(bucket_name).versioning_status
|
||||||
|
|
||||||
def get_bucket_versions(self, bucket_name, delimiter=None,
|
def get_bucket_versions(self, bucket_name, delimiter=None,
|
||||||
encoding_type=None,
|
encoding_type=None,
|
||||||
key_marker=None,
|
key_marker=None,
|
||||||
max_keys=None,
|
max_keys=None,
|
||||||
version_id_marker=None):
|
version_id_marker=None):
|
||||||
bucket = self.buckets[bucket_name]
|
bucket = self.get_bucket(bucket_name)
|
||||||
|
|
||||||
if any((delimiter, encoding_type, key_marker, version_id_marker)):
|
if any((delimiter, encoding_type, key_marker, version_id_marker)):
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
"Called get_bucket_versions with some of delimiter, encoding_type, key_marker, version_id_marker")
|
"Called get_bucket_versions with some of delimiter, encoding_type, key_marker, version_id_marker")
|
||||||
|
|
||||||
return itertools.chain(*(l for _, l in bucket.keys.iterlists()))
|
return itertools.chain(*(l for _, l in bucket.keys.iterlists()))
|
||||||
|
|
||||||
def set_key(self, bucket_name, key_name, value, storage=None, etag=None):
|
def set_key(self, bucket_name, key_name, value, storage=None, etag=None):
|
||||||
key_name = clean_key_name(key_name)
|
key_name = clean_key_name(key_name)
|
||||||
|
|
||||||
bucket = self.buckets[bucket_name]
|
bucket = self.get_bucket(bucket_name)
|
||||||
|
|
||||||
old_key = bucket.keys.get(key_name, None)
|
old_key = bucket.keys.get(key_name, None)
|
||||||
if old_key is not None and bucket.is_versioned:
|
if old_key is not None and bucket.is_versioned:
|
||||||
@ -248,14 +250,14 @@ class S3Backend(BaseBackend):
|
|||||||
return key
|
return key
|
||||||
|
|
||||||
def initiate_multipart(self, bucket_name, key_name):
|
def initiate_multipart(self, bucket_name, key_name):
|
||||||
bucket = self.buckets[bucket_name]
|
bucket = self.get_bucket(bucket_name)
|
||||||
new_multipart = FakeMultipart(key_name)
|
new_multipart = FakeMultipart(key_name)
|
||||||
bucket.multiparts[new_multipart.id] = new_multipart
|
bucket.multiparts[new_multipart.id] = new_multipart
|
||||||
|
|
||||||
return new_multipart
|
return new_multipart
|
||||||
|
|
||||||
def complete_multipart(self, bucket_name, multipart_id):
|
def complete_multipart(self, bucket_name, multipart_id):
|
||||||
bucket = self.buckets[bucket_name]
|
bucket = self.get_bucket(bucket_name)
|
||||||
multipart = bucket.multiparts[multipart_id]
|
multipart = bucket.multiparts[multipart_id]
|
||||||
value, etag = multipart.complete()
|
value, etag = multipart.complete()
|
||||||
if value is None:
|
if value is None:
|
||||||
@ -265,27 +267,27 @@ class S3Backend(BaseBackend):
|
|||||||
return self.set_key(bucket_name, multipart.key_name, value, etag=etag)
|
return self.set_key(bucket_name, multipart.key_name, value, etag=etag)
|
||||||
|
|
||||||
def cancel_multipart(self, bucket_name, multipart_id):
|
def cancel_multipart(self, bucket_name, multipart_id):
|
||||||
bucket = self.buckets[bucket_name]
|
bucket = self.get_bucket(bucket_name)
|
||||||
del bucket.multiparts[multipart_id]
|
del bucket.multiparts[multipart_id]
|
||||||
|
|
||||||
def list_multipart(self, bucket_name, multipart_id):
|
def list_multipart(self, bucket_name, multipart_id):
|
||||||
bucket = self.buckets[bucket_name]
|
bucket = self.get_bucket(bucket_name)
|
||||||
return bucket.multiparts[multipart_id].list_parts()
|
return bucket.multiparts[multipart_id].list_parts()
|
||||||
|
|
||||||
def get_all_multiparts(self, bucket_name):
|
def get_all_multiparts(self, bucket_name):
|
||||||
bucket = self.buckets[bucket_name]
|
bucket = self.get_bucket(bucket_name)
|
||||||
return bucket.multiparts
|
return bucket.multiparts
|
||||||
|
|
||||||
def set_part(self, bucket_name, multipart_id, part_id, value):
|
def set_part(self, bucket_name, multipart_id, part_id, value):
|
||||||
bucket = self.buckets[bucket_name]
|
bucket = self.get_bucket(bucket_name)
|
||||||
multipart = bucket.multiparts[multipart_id]
|
multipart = bucket.multiparts[multipart_id]
|
||||||
return multipart.set_part(part_id, value)
|
return multipart.set_part(part_id, value)
|
||||||
|
|
||||||
def copy_part(self, dest_bucket_name, multipart_id, part_id,
|
def copy_part(self, dest_bucket_name, multipart_id, part_id,
|
||||||
src_bucket_name, src_key_name):
|
src_bucket_name, src_key_name):
|
||||||
src_key_name = clean_key_name(src_key_name)
|
src_key_name = clean_key_name(src_key_name)
|
||||||
src_bucket = self.buckets[src_bucket_name]
|
src_bucket = self.get_bucket(src_bucket_name)
|
||||||
dest_bucket = self.buckets[dest_bucket_name]
|
dest_bucket = self.get_bucket(dest_bucket_name)
|
||||||
multipart = dest_bucket.multiparts[multipart_id]
|
multipart = dest_bucket.multiparts[multipart_id]
|
||||||
return multipart.set_part(part_id, src_bucket.keys[src_key_name].value)
|
return multipart.set_part(part_id, src_bucket.keys[src_key_name].value)
|
||||||
|
|
||||||
@ -317,14 +319,14 @@ class S3Backend(BaseBackend):
|
|||||||
|
|
||||||
def delete_key(self, bucket_name, key_name):
|
def delete_key(self, bucket_name, key_name):
|
||||||
key_name = clean_key_name(key_name)
|
key_name = clean_key_name(key_name)
|
||||||
bucket = self.buckets[bucket_name]
|
bucket = self.get_bucket(bucket_name)
|
||||||
return bucket.keys.pop(key_name)
|
return bucket.keys.pop(key_name)
|
||||||
|
|
||||||
def copy_key(self, src_bucket_name, src_key_name, dest_bucket_name, dest_key_name, storage=None):
|
def copy_key(self, src_bucket_name, src_key_name, dest_bucket_name, dest_key_name, storage=None):
|
||||||
src_key_name = clean_key_name(src_key_name)
|
src_key_name = clean_key_name(src_key_name)
|
||||||
dest_key_name = clean_key_name(dest_key_name)
|
dest_key_name = clean_key_name(dest_key_name)
|
||||||
src_bucket = self.buckets[src_bucket_name]
|
src_bucket = self.get_bucket(src_bucket_name)
|
||||||
dest_bucket = self.buckets[dest_bucket_name]
|
dest_bucket = self.get_bucket(dest_bucket_name)
|
||||||
key = src_bucket.keys[src_key_name]
|
key = src_bucket.keys[src_key_name]
|
||||||
if dest_key_name != src_key_name:
|
if dest_key_name != src_key_name:
|
||||||
key = key.copy(dest_key_name)
|
key = key.copy(dest_key_name)
|
||||||
|
@ -3,7 +3,7 @@ import re
|
|||||||
|
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
|
|
||||||
from .exceptions import BucketAlreadyExists
|
from .exceptions import BucketAlreadyExists, MissingBucket
|
||||||
from .models import s3_backend
|
from .models import s3_backend
|
||||||
from .utils import bucket_name_from_url
|
from .utils import bucket_name_from_url
|
||||||
from xml.dom import minidom
|
from xml.dom import minidom
|
||||||
@ -26,7 +26,11 @@ class ResponseObject(object):
|
|||||||
return template.render(buckets=all_buckets)
|
return template.render(buckets=all_buckets)
|
||||||
|
|
||||||
def bucket_response(self, request, full_url, headers):
|
def bucket_response(self, request, full_url, headers):
|
||||||
response = self._bucket_response(request, full_url, headers)
|
try:
|
||||||
|
response = self._bucket_response(request, full_url, headers)
|
||||||
|
except MissingBucket:
|
||||||
|
return 404, headers, ""
|
||||||
|
|
||||||
if isinstance(response, basestring):
|
if isinstance(response, basestring):
|
||||||
return 200, headers, response
|
return 200, headers, response
|
||||||
else:
|
else:
|
||||||
@ -57,11 +61,12 @@ class ResponseObject(object):
|
|||||||
raise NotImplementedError("Method {0} has not been impelemented in the S3 backend yet".format(method))
|
raise NotImplementedError("Method {0} has not been impelemented in the S3 backend yet".format(method))
|
||||||
|
|
||||||
def _bucket_response_head(self, bucket_name, headers):
|
def _bucket_response_head(self, bucket_name, headers):
|
||||||
bucket = self.backend.get_bucket(bucket_name)
|
try:
|
||||||
if bucket:
|
self.backend.get_bucket(bucket_name)
|
||||||
return 200, headers, ""
|
except MissingBucket:
|
||||||
else:
|
|
||||||
return 404, headers, ""
|
return 404, headers, ""
|
||||||
|
else:
|
||||||
|
return 200, headers, ""
|
||||||
|
|
||||||
def _bucket_response_get(self, bucket_name, querystring, headers):
|
def _bucket_response_get(self, bucket_name, querystring, headers):
|
||||||
if 'uploads' in querystring:
|
if 'uploads' in querystring:
|
||||||
@ -104,22 +109,23 @@ class ResponseObject(object):
|
|||||||
is_truncated='false',
|
is_truncated='false',
|
||||||
)
|
)
|
||||||
|
|
||||||
bucket = self.backend.get_bucket(bucket_name)
|
try:
|
||||||
if bucket:
|
bucket = self.backend.get_bucket(bucket_name)
|
||||||
prefix = querystring.get('prefix', [None])[0]
|
except MissingBucket:
|
||||||
delimiter = querystring.get('delimiter', [None])[0]
|
|
||||||
result_keys, result_folders = self.backend.prefix_query(bucket, prefix, delimiter)
|
|
||||||
template = Template(S3_BUCKET_GET_RESPONSE)
|
|
||||||
return 200, headers, template.render(
|
|
||||||
bucket=bucket,
|
|
||||||
prefix=prefix,
|
|
||||||
delimiter=delimiter,
|
|
||||||
result_keys=result_keys,
|
|
||||||
result_folders=result_folders
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return 404, headers, ""
|
return 404, headers, ""
|
||||||
|
|
||||||
|
prefix = querystring.get('prefix', [None])[0]
|
||||||
|
delimiter = querystring.get('delimiter', [None])[0]
|
||||||
|
result_keys, result_folders = self.backend.prefix_query(bucket, prefix, delimiter)
|
||||||
|
template = Template(S3_BUCKET_GET_RESPONSE)
|
||||||
|
return 200, headers, template.render(
|
||||||
|
bucket=bucket,
|
||||||
|
prefix=prefix,
|
||||||
|
delimiter=delimiter,
|
||||||
|
result_keys=result_keys,
|
||||||
|
result_folders=result_folders
|
||||||
|
)
|
||||||
|
|
||||||
def _bucket_response_put(self, request, bucket_name, querystring, headers):
|
def _bucket_response_put(self, request, bucket_name, querystring, headers):
|
||||||
if 'versioning' in querystring:
|
if 'versioning' in querystring:
|
||||||
ver = re.search('<Status>([A-Za-z]+)</Status>', request.body)
|
ver = re.search('<Status>([A-Za-z]+)</Status>', request.body)
|
||||||
@ -138,12 +144,14 @@ class ResponseObject(object):
|
|||||||
return 200, headers, template.render(bucket=new_bucket)
|
return 200, headers, template.render(bucket=new_bucket)
|
||||||
|
|
||||||
def _bucket_response_delete(self, bucket_name, headers):
|
def _bucket_response_delete(self, bucket_name, headers):
|
||||||
removed_bucket = self.backend.delete_bucket(bucket_name)
|
try:
|
||||||
if removed_bucket is None:
|
removed_bucket = self.backend.delete_bucket(bucket_name)
|
||||||
|
except MissingBucket:
|
||||||
# Non-existant bucket
|
# Non-existant bucket
|
||||||
template = Template(S3_DELETE_NON_EXISTING_BUCKET)
|
template = Template(S3_DELETE_NON_EXISTING_BUCKET)
|
||||||
return 404, headers, template.render(bucket_name=bucket_name)
|
return 404, headers, template.render(bucket_name=bucket_name)
|
||||||
elif removed_bucket:
|
|
||||||
|
if removed_bucket:
|
||||||
# Bucket exists
|
# Bucket exists
|
||||||
template = Template(S3_DELETE_BUCKET_SUCCESS)
|
template = Template(S3_DELETE_BUCKET_SUCCESS)
|
||||||
return 204, headers, template.render(bucket=removed_bucket)
|
return 204, headers, template.render(bucket=removed_bucket)
|
||||||
@ -198,13 +206,17 @@ class ResponseObject(object):
|
|||||||
key_name = k.firstChild.nodeValue
|
key_name = k.firstChild.nodeValue
|
||||||
self.backend.delete_key(bucket_name, key_name)
|
self.backend.delete_key(bucket_name, key_name)
|
||||||
deleted_names.append(key_name)
|
deleted_names.append(key_name)
|
||||||
except KeyError as e:
|
except KeyError:
|
||||||
error_names.append(key_name)
|
error_names.append(key_name)
|
||||||
|
|
||||||
return 200, headers, template.render(deleted=deleted_names,delete_errors=error_names)
|
return 200, headers, template.render(deleted=deleted_names, delete_errors=error_names)
|
||||||
|
|
||||||
def key_response(self, request, full_url, headers):
|
def key_response(self, request, full_url, headers):
|
||||||
response = self._key_response(request, full_url, headers)
|
try:
|
||||||
|
response = self._key_response(request, full_url, headers)
|
||||||
|
except MissingBucket:
|
||||||
|
return 404, headers, ""
|
||||||
|
|
||||||
if isinstance(response, basestring):
|
if isinstance(response, basestring):
|
||||||
return 200, headers, response
|
return 200, headers, response
|
||||||
else:
|
else:
|
||||||
@ -455,43 +467,43 @@ S3_DELETE_BUCKET_WITH_ITEMS_ERROR = """<?xml version="1.0" encoding="UTF-8"?>
|
|||||||
S3_BUCKET_VERSIONING = """
|
S3_BUCKET_VERSIONING = """
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||||
<Status>{{ bucket_versioning_status }}</Status>
|
<Status>{{ bucket_versioning_status }}</Status>
|
||||||
</VersioningConfiguration>
|
</VersioningConfiguration>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
S3_BUCKET_GET_VERSIONING = """
|
S3_BUCKET_GET_VERSIONING = """
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
{% if status is none %}
|
{% if status is none %}
|
||||||
<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"/>
|
<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"/>
|
||||||
{% else %}
|
{% else %}
|
||||||
<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||||
<Status>{{ status }}</Status>
|
<Status>{{ status }}</Status>
|
||||||
</VersioningConfiguration>
|
</VersioningConfiguration>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
S3_BUCKET_GET_VERSIONS = """<?xml version="1.0" encoding="UTF-8"?>
|
S3_BUCKET_GET_VERSIONS = """<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<ListVersionsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01">
|
<ListVersionsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01">
|
||||||
<Name>{{ bucket.name }}</Name>
|
<Name>{{ bucket.name }}</Name>
|
||||||
<Prefix>{{ prefix }}</Prefix>
|
<Prefix>{{ prefix }}</Prefix>
|
||||||
<KeyMarker>{{ key_marker }}</KeyMarker>
|
<KeyMarker>{{ key_marker }}</KeyMarker>
|
||||||
<MaxKeys>{{ max_keys }}</MaxKeys>
|
<MaxKeys>{{ max_keys }}</MaxKeys>
|
||||||
<IsTruncated>{{ is_truncated }}</IsTruncated>
|
<IsTruncated>{{ is_truncated }}</IsTruncated>
|
||||||
{% for key in key_list %}
|
{% for key in key_list %}
|
||||||
<Version>
|
<Version>
|
||||||
<Key>{{ key.name }}</Key>
|
<Key>{{ key.name }}</Key>
|
||||||
<VersionId>{{ key._version_id }}</VersionId>
|
<VersionId>{{ key._version_id }}</VersionId>
|
||||||
<IsLatest>false</IsLatest>
|
<IsLatest>false</IsLatest>
|
||||||
<LastModified>{{ key.last_modified_ISO8601 }}</LastModified>
|
<LastModified>{{ key.last_modified_ISO8601 }}</LastModified>
|
||||||
<ETag>{{ key.etag }}</ETag>
|
<ETag>{{ key.etag }}</ETag>
|
||||||
<Size>{{ key.size }}</Size>
|
<Size>{{ key.size }}</Size>
|
||||||
<StorageClass>{{ key.storage_class }}</StorageClass>
|
<StorageClass>{{ key.storage_class }}</StorageClass>
|
||||||
<Owner>
|
<Owner>
|
||||||
<ID>75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a</ID>
|
<ID>75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a</ID>
|
||||||
<DisplayName>webfile</DisplayName>
|
<DisplayName>webfile</DisplayName>
|
||||||
</Owner>
|
</Owner>
|
||||||
</Version>
|
</Version>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ListVersionsResult>
|
</ListVersionsResult>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -149,6 +149,16 @@ def test_list_multiparts():
|
|||||||
uploads.should.be.empty
|
uploads.should.be.empty
|
||||||
|
|
||||||
|
|
||||||
|
@mock_s3
|
||||||
|
def test_key_save_to_missing_bucket():
|
||||||
|
conn = boto.connect_s3('the_key', 'the_secret')
|
||||||
|
bucket = conn.get_bucket('mybucket', validate=False)
|
||||||
|
|
||||||
|
key = Key(bucket)
|
||||||
|
key.key = "the-key"
|
||||||
|
key.set_contents_from_string.when.called_with("foobar").should.throw(S3ResponseError)
|
||||||
|
|
||||||
|
|
||||||
@mock_s3
|
@mock_s3
|
||||||
def test_missing_key():
|
def test_missing_key():
|
||||||
conn = boto.connect_s3('the_key', 'the_secret')
|
conn = boto.connect_s3('the_key', 'the_secret')
|
||||||
|
Loading…
Reference in New Issue
Block a user