Skip to content

Commit 35730b4

Browse files
committed
Merge pull request #1217 from tseaver/651-support_predefined_acls_by_name
Add 'ACL.save_predefined' method.
2 parents 5d589a1 + 14fe52d commit 35730b4

File tree

2 files changed

+116
-13
lines changed

2 files changed

+116
-13
lines changed

gcloud/storage/acl.py

Lines changed: 67 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,21 @@ class ACL(object):
166166
"""Container class representing a list of access controls."""
167167

168168
_URL_PATH_ELEM = 'acl'
169+
_PREDEFINED_QUERY_PARAM = 'predefinedAcl'
170+
171+
_PREDEFINED_ACLS = frozenset([
172+
'private',
173+
'project-private',
174+
'public-read',
175+
'public-read-write',
176+
'authenticated-read',
177+
'bucket-owner-read',
178+
'bucket-owner-full-control',
179+
])
180+
"""See:
181+
https://cloud.google.com/storage/docs/access-control#predefined-acl
182+
"""
183+
169184
loaded = False
170185

171186
# Subclasses must override to provide these attributes (typically,
@@ -385,6 +400,39 @@ def reload(self, client=None):
385400
for entry in found.get('items', ()):
386401
self.add_entity(self.entity_from_dict(entry))
387402

403+
def _save(self, acl, predefined, client):
404+
"""Helper for :meth:`save` and :meth:`save_predefined`.
405+
406+
:type acl: :class:`gcloud.storage.acl.ACL`, or a compatible list.
407+
:param acl: The ACL object to save. If left blank, this will save
408+
current entries.
409+
410+
:type predefined: string or None
411+
:param predefined: An identifier for a predefined ACL. Must be one
412+
of the keys in :attr:`_PREDEFINED_ACLS`
413+
If passed, `acl` must be None.
414+
415+
:type client: :class:`gcloud.storage.client.Client` or ``NoneType``
416+
:param client: Optional. The client to use. If not passed, falls back
417+
to the ``client`` stored on the ACL's parent.
418+
"""
419+
query_params = {'projection': 'full'}
420+
if predefined is not None:
421+
acl = []
422+
query_params[self._PREDEFINED_QUERY_PARAM] = predefined
423+
424+
path = self.save_path
425+
client = self._require_client(client)
426+
result = client.connection.api_request(
427+
method='PATCH',
428+
path=path,
429+
data={self._URL_PATH_ELEM: list(acl)},
430+
query_params=query_params)
431+
self.entities.clear()
432+
for entry in result.get(self._URL_PATH_ELEM, ()):
433+
self.add_entity(self.entity_from_dict(entry))
434+
self.loaded = True
435+
388436
def save(self, acl=None, client=None):
389437
"""Save this ACL for the current bucket.
390438
@@ -403,17 +451,24 @@ def save(self, acl=None, client=None):
403451
save_to_backend = True
404452

405453
if save_to_backend:
406-
path = self.save_path
407-
client = self._require_client(client)
408-
result = client.connection.api_request(
409-
method='PATCH',
410-
path=path,
411-
data={self._URL_PATH_ELEM: list(acl)},
412-
query_params={'projection': 'full'})
413-
self.entities.clear()
414-
for entry in result.get(self._URL_PATH_ELEM, ()):
415-
self.add_entity(self.entity_from_dict(entry))
416-
self.loaded = True
454+
self._save(acl, None, client)
455+
456+
def save_predefined(self, predefined, client=None):
457+
"""Save this ACL for the current bucket using a predefined ACL.
458+
459+
:type predefined: string
460+
:param predefined: An identifier for a predefined ACL. Must be one
461+
of the keys in :attr:`_PREDEFINED_ACLS`
462+
If passed, `acl` must be None.
463+
464+
:type client: :class:`gcloud.storage.client.Client` or ``NoneType``
465+
:param client: Optional. The client to use. If not passed, falls back
466+
to the ``client`` stored on the ACL's parent.
467+
"""
468+
if predefined not in self._PREDEFINED_ACLS:
469+
raise ValueError("Invalid predefined ACL: %s" % (predefined,))
470+
471+
self._save(None, predefined, client)
417472

418473
def clear(self, client=None):
419474
"""Remove all ACL entries.
@@ -461,6 +516,7 @@ class DefaultObjectACL(BucketACL):
461516
"""A class representing the default object ACL for a bucket."""
462517

463518
_URL_PATH_ELEM = 'defaultObjectAcl'
519+
_PREDEFINED_QUERY_PARAM = 'predefinedDefaultObjectAcl'
464520

465521

466522
class ObjectACL(ACL):

gcloud/storage/test_acl.py

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,7 @@ def test_save_existing_missing_none_passed(self):
581581
self.assertEqual(kw[0]['data'], {'acl': []})
582582
self.assertEqual(kw[0]['query_params'], {'projection': 'full'})
583583

584-
def test_save_no_arg(self):
584+
def test_save_no_acl(self):
585585
ROLE = 'role'
586586
AFTER = [{'entity': 'allUsers', 'role': ROLE}]
587587
connection = _Connection({'acl': AFTER})
@@ -599,7 +599,7 @@ def test_save_no_arg(self):
599599
self.assertEqual(kw[0]['data'], {'acl': AFTER})
600600
self.assertEqual(kw[0]['query_params'], {'projection': 'full'})
601601

602-
def test_save_w_arg(self):
602+
def test_save_w_acl(self):
603603
ROLE1 = 'role1'
604604
ROLE2 = 'role2'
605605
STICKY = {'entity': 'allUsers', 'role': ROLE2}
@@ -621,6 +621,53 @@ def test_save_w_arg(self):
621621
self.assertEqual(kw[0]['data'], {'acl': new_acl})
622622
self.assertEqual(kw[0]['query_params'], {'projection': 'full'})
623623

624+
def test_save_prefefined_invalid(self):
625+
connection = _Connection()
626+
client = _Client(connection)
627+
acl = self._makeOne()
628+
acl.save_path = '/testing'
629+
acl.loaded = True
630+
with self.assertRaises(ValueError):
631+
acl.save_predefined('bogus', client=client)
632+
633+
def test_save_predefined_valid(self):
634+
PREDEFINED = 'private'
635+
connection = _Connection({'acl': []})
636+
client = _Client(connection)
637+
acl = self._makeOne()
638+
acl.save_path = '/testing'
639+
acl.loaded = True
640+
acl.save_predefined(PREDEFINED, client=client)
641+
entries = list(acl)
642+
self.assertEqual(len(entries), 0)
643+
kw = connection._requested
644+
self.assertEqual(len(kw), 1)
645+
self.assertEqual(kw[0]['method'], 'PATCH')
646+
self.assertEqual(kw[0]['path'], '/testing')
647+
self.assertEqual(kw[0]['data'], {'acl': []})
648+
self.assertEqual(kw[0]['query_params'],
649+
{'projection': 'full', 'predefinedAcl': PREDEFINED})
650+
651+
def test_save_predefined_valid_w_alternate_query_param(self):
652+
# Cover case where subclass overrides _PREDEFINED_QUERY_PARAM
653+
PREDEFINED = 'private'
654+
connection = _Connection({'acl': []})
655+
client = _Client(connection)
656+
acl = self._makeOne()
657+
acl.save_path = '/testing'
658+
acl.loaded = True
659+
acl._PREDEFINED_QUERY_PARAM = 'alternate'
660+
acl.save_predefined(PREDEFINED, client=client)
661+
entries = list(acl)
662+
self.assertEqual(len(entries), 0)
663+
kw = connection._requested
664+
self.assertEqual(len(kw), 1)
665+
self.assertEqual(kw[0]['method'], 'PATCH')
666+
self.assertEqual(kw[0]['path'], '/testing')
667+
self.assertEqual(kw[0]['data'], {'acl': []})
668+
self.assertEqual(kw[0]['query_params'],
669+
{'projection': 'full', 'alternate': PREDEFINED})
670+
624671
def test_clear(self):
625672
ROLE1 = 'role1'
626673
ROLE2 = 'role2'

0 commit comments

Comments
 (0)