Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#825: Allow passing explicit connection to ACL.{reload,save} #853

Merged
merged 7 commits into from
May 6, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 57 additions & 110 deletions gcloud/storage/acl.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@
when sending metadata for ACLs to the API.
"""

from gcloud.storage._helpers import _require_connection


class _ACLEntity(object):
"""Class representing a set of roles for an entity.
Expand Down Expand Up @@ -167,8 +169,14 @@ def revoke_owner(self):
class ACL(object):
"""Container class representing a list of access controls."""

_URL_PATH_ELEM = 'acl'
loaded = False

# Subclasses must override to provide these attributes (typically,
# as properties).
reload_path = None
save_path = None

def __init__(self):
self.entities = {}

Expand Down Expand Up @@ -345,77 +353,33 @@ def get_entities(self):
self._ensure_loaded()
return list(self.entities.values())

def reload(self):
def reload(self, connection=None):
"""Reload the ACL data from Cloud Storage.

This is a virtual method, expected to be implemented by subclasses.

:raises: :class:`NotImplementedError`
"""
raise NotImplementedError

def save(self, acl=None):
"""A method to be overridden by subclasses.

:type acl: :class:`gcloud.storage.acl.ACL`, or a compatible list.
:param acl: The ACL object to save. If left blank, this will save
current entries.

:raises: NotImplementedError
"""
raise NotImplementedError

def clear(self):
"""Remove all entities from the ACL."""
raise NotImplementedError


class BucketACL(ACL):
"""An ACL specifically for a bucket."""

_URL_PATH_ELEM = 'acl'

def __init__(self, bucket):
:type connection: :class:`gcloud.storage.connection.Connection` or None
:param connection: explicit connection to use for API request;
defaults to instance property.
"""
:type bucket: :class:`gcloud.storage.bucket.Bucket`
:param bucket: The bucket to which this ACL relates.
"""
super(BucketACL, self).__init__()
self.bucket = bucket
path = self.reload_path
connection = _require_connection(connection)

def reload(self):
"""Reload the ACL data from Cloud Storage."""
self.entities.clear()

url_path = '%s/%s' % (self.bucket.path, self._URL_PATH_ELEM)
found = self.bucket.connection.api_request(method='GET', path=url_path)
found = connection.api_request(method='GET', path=path)
self.loaded = True
for entry in found.get('items', ()):
self.add_entity(self.entity_from_dict(entry))

def save(self, acl=None):
def save(self, acl=None, connection=None):
"""Save this ACL for the current bucket.

If called without arguments, this will save the entries
currently stored on this ACL::

>>> acl.save()

You can also provide a specific ACL to save instead of the one
currently set on the Bucket object::

>>> acl.save(acl=my_other_acl)

You can use this to set access controls to be consistent from
one bucket to another::

>>> bucket1 = storage.get_bucket(bucket1_name, connection=connection)
>>> bucket2 = storage.get_bucket(bucket2_name, connection=connection)
>>> bucket2.acl.save(bucket1.acl)

:type acl: :class:`gcloud.storage.acl.ACL`, or a compatible list.
:param acl: The ACL object to save. If left blank, this will save
current entries.

:type connection: :class:`gcloud.storage.connection.Connection` or None
:param connection: explicit connection to use for API request;
defaults to instance property.
"""
if acl is None:
acl = self
Expand All @@ -424,38 +388,53 @@ def save(self, acl=None):
save_to_backend = True

if save_to_backend:
result = self.bucket.connection.api_request(
method='PATCH', path=self.bucket.path,
path = self.save_path
connection = _require_connection(connection)
result = connection.api_request(
method='PATCH',
path=path,
data={self._URL_PATH_ELEM: list(acl)},
query_params={'projection': 'full'})
self.entities.clear()
for entry in result.get(self._URL_PATH_ELEM, ()):
self.add_entity(self.entity_from_dict(entry))
self.loaded = True

def clear(self):
def clear(self, connection=None):
"""Remove all ACL entries.

Note that this won't actually remove *ALL* the rules, but it
will remove all the non-default rules. In short, you'll still
have access to a bucket that you created even after you clear
ACL rules with this method.

For example, imagine that you granted access to this bucket to a
bunch of coworkers::

>>> acl.user('[email protected]').grant_read()
>>> acl.user('[email protected]').grant_read()
>>> acl.save()
:type connection: :class:`gcloud.storage.connection.Connection` or None
:param connection: explicit connection to use for API request;
defaults to instance property.
"""
self.save([], connection)

Now they work in another part of the company and you want to
'start fresh' on who has access::

>>> acl.clear()
class BucketACL(ACL):
"""An ACL specifically for a bucket."""

At this point all the custom rules you created have been removed.
def __init__(self, bucket):
"""
self.save([])
:type bucket: :class:`gcloud.storage.bucket.Bucket`
:param bucket: The bucket to which this ACL relates.
"""
super(BucketACL, self).__init__()
self.bucket = bucket

@property
def reload_path(self):
"""Compute the path for GET API requests for this ACL."""
return '%s/%s' % (self.bucket.path, self._URL_PATH_ELEM)

@property
def save_path(self):
"""Compute the path for PATCH API requests for this ACL."""
return self.bucket.path


class DefaultObjectACL(BucketACL):
Expand All @@ -475,44 +454,12 @@ def __init__(self, blob):
super(ObjectACL, self).__init__()
self.blob = blob

def reload(self):
"""Reload the ACL data from Cloud Storage."""
self.entities.clear()

url_path = '%s/acl' % self.blob.path
found = self.blob.connection.api_request(method='GET', path=url_path)
self.loaded = True
for entry in found.get('items', ()):
self.add_entity(self.entity_from_dict(entry))

def save(self, acl=None):
"""Save the ACL data for this blob.
@property
def reload_path(self):
"""Compute the path for GET API requests for this ACL."""
return '%s/acl' % self.blob.path

:type acl: :class:`gcloud.storage.acl.ACL`
:param acl: The ACL object to save. If left blank, this will
save the entries set locally on the ACL.
"""
if acl is None:
acl = self
save_to_backend = acl.loaded
else:
save_to_backend = True

if save_to_backend:
result = self.blob.connection.api_request(
method='PATCH', path=self.blob.path, data={'acl': list(acl)},
query_params={'projection': 'full'})
self.entities.clear()
for entry in result.get('acl', ()):
self.add_entity(self.entity_from_dict(entry))
self.loaded = True

def clear(self):
"""Remove all ACL rules from the blob.

Note that this won't actually remove *ALL* the rules, but it
will remove all the non-default rules. In short, you'll still
have access to a blob that you created even after you clear ACL
rules with this method.
"""
self.save([])
@property
def save_path(self):
"""Compute the path for PATCH API requests for this ACL."""
return self.blob.path
Loading