Skip to content

Commit ab58dd5

Browse files
committed
Add lifecycle support to buckets.
See: https://cloud.google.com/storage/docs/lifecycle and https://cloud.google.com/storage/docs/json_api/v1/buckets Addresses 'lifecycle' part of 314.
1 parent 8b32e34 commit ab58dd5

File tree

2 files changed

+94
-0
lines changed

2 files changed

+94
-0
lines changed

gcloud/storage/bucket.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class Bucket(_MetadataMixin):
2424
CUSTOM_METADATA_FIELDS = {
2525
'acl': 'get_acl',
2626
'defaultObjectAcl': 'get_default_object_acl',
27+
'lifecycle': 'get_lifecycle',
2728
}
2829
"""Mapping of field name -> accessor for fields w/ custom accessors."""
2930

@@ -441,6 +442,35 @@ def make_public(self, recursive=False, future=False):
441442
key.get_acl().all().grant_read()
442443
key.save_acl()
443444

445+
def get_lifecycle(self):
446+
"""Retrieve CORS policies configured for this bucket.
447+
448+
See: https://cloud.google.com/storage/docs/lifecycle and
449+
https://cloud.google.com/storage/docs/json_api/v1/buckets
450+
451+
:rtype: list(dict)
452+
:returns: A sequence of mappings describing each CORS policy.
453+
"""
454+
if not self.has_metadata('lifecycle'):
455+
self.reload_metadata()
456+
result = []
457+
info = self.metadata.get('lifecycle', {})
458+
for rule in info.get('rule', ()):
459+
rule = rule.copy()
460+
result.append(rule)
461+
return result
462+
463+
def update_lifecycle(self, rules):
464+
"""Update CORS policies configured for this bucket.
465+
466+
See: https://cloud.google.com/storage/docs/lifecycle and
467+
https://cloud.google.com/storage/docs/json_api/v1/buckets
468+
469+
:type rules: list(dict)
470+
:param rules: A sequence of mappings describing each lifecycle policy.
471+
"""
472+
self.patch_metadata({'lifecycle': {'rule': rules}})
473+
444474

445475
class BucketIterator(Iterator):
446476
"""An iterator listing all buckets.

gcloud/storage/test_bucket.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,23 @@ def test_get_metadata_none_set_defaultObjectAcl_miss_clear_default(self):
489489
kw = connection._requested
490490
self.assertEqual(len(kw), 0)
491491

492+
def test_get_metadata_lifecycle_no_default(self):
493+
NAME = 'name'
494+
connection = _Connection()
495+
bucket = self._makeOne(connection, NAME)
496+
self.assertRaises(KeyError, bucket.get_metadata, 'lifecycle')
497+
kw = connection._requested
498+
self.assertEqual(len(kw), 0)
499+
500+
def test_get_metadata_lifecycle_w_default(self):
501+
NAME = 'name'
502+
connection = _Connection()
503+
bucket = self._makeOne(connection, NAME)
504+
default = object()
505+
self.assertRaises(KeyError, bucket.get_metadata, 'lifecycle', default)
506+
kw = connection._requested
507+
self.assertEqual(len(kw), 0)
508+
492509
def test_get_metadata_miss(self):
493510
NAME = 'name'
494511
before = {'bar': 'Bar'}
@@ -713,6 +730,53 @@ def get_items_from_response(self, response):
713730
self.assertEqual(kw[1]['path'], '/b/%s/o' % NAME)
714731
self.assertEqual(kw[1]['query_params'], None)
715732

733+
def test_get_lifecycle_eager(self):
734+
NAME = 'name'
735+
LC_RULE = {'action': {'type': 'Delete'}, 'condition': {'age': 42}}
736+
before = {'lifecycle': {'rule': [LC_RULE]}}
737+
connection = _Connection()
738+
bucket = self._makeOne(connection, NAME, before)
739+
entries = bucket.get_lifecycle()
740+
self.assertEqual(len(entries), 1)
741+
self.assertEqual(entries[0]['action']['type'], 'Delete')
742+
self.assertEqual(entries[0]['condition']['age'], 42)
743+
kw = connection._requested
744+
self.assertEqual(len(kw), 0)
745+
746+
def test_get_lifecycle_lazy(self):
747+
NAME = 'name'
748+
LC_RULE = {'action': {'type': 'Delete'}, 'condition': {'age': 42}}
749+
after = {'lifecycle': {'rule': [LC_RULE]}}
750+
connection = _Connection(after)
751+
bucket = self._makeOne(connection, NAME)
752+
entries = bucket.get_lifecycle()
753+
self.assertEqual(len(entries), 1)
754+
self.assertEqual(entries[0]['action']['type'], 'Delete')
755+
self.assertEqual(entries[0]['condition']['age'], 42)
756+
kw = connection._requested
757+
self.assertEqual(len(kw), 1)
758+
self.assertEqual(kw[0]['method'], 'GET')
759+
self.assertEqual(kw[0]['path'], '/b/%s' % NAME)
760+
self.assertEqual(kw[0]['query_params'], {'projection': 'noAcl'})
761+
762+
def test_update_lifecycle(self):
763+
NAME = 'name'
764+
LC_RULE = {'action': {'type': 'Delete'}, 'condition': {'age': 42}}
765+
after = {'lifecycle': {'rule': [LC_RULE]}}
766+
connection = _Connection(after)
767+
bucket = self._makeOne(connection, NAME)
768+
bucket.update_lifecycle([LC_RULE])
769+
kw = connection._requested
770+
self.assertEqual(len(kw), 1)
771+
self.assertEqual(kw[0]['method'], 'PATCH')
772+
self.assertEqual(kw[0]['path'], '/b/%s' % NAME)
773+
self.assertEqual(kw[0]['data'], after)
774+
self.assertEqual(kw[0]['query_params'], {'projection': 'full'})
775+
entries = bucket.get_lifecycle()
776+
self.assertEqual(len(entries), 1)
777+
self.assertEqual(entries[0]['action']['type'], 'Delete')
778+
self.assertEqual(entries[0]['condition']['age'], 42)
779+
716780

717781
class TestBucketIterator(unittest2.TestCase):
718782

0 commit comments

Comments
 (0)