Skip to content
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
28 changes: 21 additions & 7 deletions storage/google/cloud/storage/blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,9 @@ def __init__(self, name, bucket, chunk_size=None,
"and 'kms_key_name'")

self._encryption_key = encryption_key
self._kms_key_name = kms_key_name

if kms_key_name is not None:
self._properties['kmsKeyName'] = kms_key_name

@property
def chunk_size(self):
Expand Down Expand Up @@ -740,8 +742,8 @@ def _do_multipart_upload(self, client, stream, content_type,
if self.user_project is not None:
name_value_pairs.append(('userProject', self.user_project))

if self._kms_key_name is not None:
name_value_pairs.append(('kmsKeyName', self._kms_key_name))
if self.kms_key_name is not None:
name_value_pairs.append(('kmsKeyName', self.kms_key_name))

if predefined_acl is not None:
name_value_pairs.append(('predefinedAcl', predefined_acl))
Expand Down Expand Up @@ -830,8 +832,8 @@ def _initiate_resumable_upload(self, client, stream, content_type,
if self.user_project is not None:
name_value_pairs.append(('userProject', self.user_project))

if self._kms_key_name is not None:
name_value_pairs.append(('kmsKeyName', self._kms_key_name))
if self.kms_key_name is not None:
name_value_pairs.append(('kmsKeyName', self.kms_key_name))

if predefined_acl is not None:
name_value_pairs.append(('predefinedAcl', predefined_acl))
Expand Down Expand Up @@ -1421,8 +1423,8 @@ def rewrite(self, source, token=None, client=None):
if self.user_project is not None:
query_params['userProject'] = self.user_project

if self._kms_key_name is not None:
query_params['destinationKmsKeyName'] = self._kms_key_name
if self.kms_key_name is not None:
query_params['destinationKmsKeyName'] = self.kms_key_name

api_response = client._connection.api_request(
method='POST',
Expand Down Expand Up @@ -1712,6 +1714,18 @@ def size(self):
if size is not None:
return int(size)

@property
def kms_key_name(self):
"""Resource name of Cloud KMS key used to encrypt the blob's contents.

See https://cloud.google.com/storage/docs/encryption#kms.

:rtype: str or ``NoneType``
:returns:
The resource name or ``None`` if the property is not set locally.
"""
return self._properties.get('kmsKeyName')

storage_class = _scalar_property('storageClass')
"""Retrieve the storage class for the object.

Expand Down
110 changes: 104 additions & 6 deletions storage/tests/unit/test_blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ class Test_Blob(unittest.TestCase):
def _make_one(*args, **kw):
from google.cloud.storage.blob import Blob

properties = kw.pop('properties', None)
properties = kw.pop('properties', {})
blob = Blob(*args, **kw)
blob._properties = properties or {}
blob._properties.update(properties)
return blob

def test_ctor_wo_encryption_key(self):
Expand All @@ -55,7 +55,7 @@ def test_ctor_wo_encryption_key(self):
self.assertFalse(blob._acl.loaded)
self.assertIs(blob._acl.blob, blob)
self.assertEqual(blob._encryption_key, None)
self.assertEqual(blob._kms_key_name, None)
self.assertEqual(blob.kms_key_name, None)

def test_ctor_with_encoded_unicode(self):
blob_name = b'wet \xe2\x9b\xb5'
Expand All @@ -71,7 +71,7 @@ def test_ctor_w_encryption_key(self):
bucket = _Bucket()
blob = self._make_one(BLOB_NAME, bucket=bucket, encryption_key=KEY)
self.assertEqual(blob._encryption_key, KEY)
self.assertEqual(blob._kms_key_name, None)
self.assertEqual(blob.kms_key_name, None)

def test_ctor_w_kms_key_name_and_encryption_key(self):
KEY = b'01234567890123456789012345678901' # 32 bytes
Expand Down Expand Up @@ -102,7 +102,103 @@ def test_ctor_w_kms_key_name(self):
blob = self._make_one(
BLOB_NAME, bucket=bucket, kms_key_name=KMS_RESOURCE)
self.assertEqual(blob._encryption_key, None)
self.assertEqual(blob._kms_key_name, KMS_RESOURCE)
self.assertEqual(blob.kms_key_name, KMS_RESOURCE)

def _set_properties_helper(self, kms_key_name=None):
import datetime
from google.cloud._helpers import UTC
from google.cloud._helpers import _RFC3339_MICROS
now = datetime.datetime.utcnow().replace(tzinfo=UTC)
NOW = now.strftime(_RFC3339_MICROS)
BLOB_NAME = 'blob-name'
GENERATION = 12345
BLOB_ID = 'name/{}/{}'.format(BLOB_NAME, GENERATION)
SELF_LINK = 'http://example.com/self/'
METAGENERATION = 23456
SIZE = 12345
MD5_HASH = 'DEADBEEF'
MEDIA_LINK = 'http://example.com/media/'
ENTITY = 'project-owner-12345'
ENTITY_ID = '23456'
CRC32C = 'FACE0DAC'
COMPONENT_COUNT = 2
ETAG = 'ETAG'
resource = {
'id': BLOB_ID,
'selfLink': SELF_LINK,
'generation': GENERATION,
'metageneration': METAGENERATION,
'contentType': 'text/plain',
'timeCreated': NOW,
'updated': NOW,
'timeDeleted': NOW,
'storageClass': 'NEARLINE',
'timeStorageClassUpdated': NOW,
'size': SIZE,
'md5Hash': MD5_HASH,
'mediaLink': MEDIA_LINK,
'contentEncoding': 'gzip',
'contentDisposition': 'inline',
'contentLanguage': 'en-US',
'cacheControl': 'private',
'metadata': {
'foo': 'Foo',
},
'owner': {
'entity': ENTITY,
'entityId': ENTITY_ID,
},
'crc32c': CRC32C,
'componentCount': COMPONENT_COUNT,
'etag': ETAG,
}

if kms_key_name is not None:
resource['kmsKeyName'] = kms_key_name

bucket = _Bucket()
blob = self._make_one(BLOB_NAME, bucket=bucket)

blob._set_properties(resource)

self.assertEqual(blob.id, BLOB_ID)
self.assertEqual(blob.self_link, SELF_LINK)
self.assertEqual(blob.generation, GENERATION)
self.assertEqual(blob.metageneration, METAGENERATION)
self.assertEqual(blob.content_type, 'text/plain')
self.assertEqual(blob.time_created, now)
self.assertEqual(blob.updated, now)
self.assertEqual(blob.time_deleted, now)
self.assertEqual(blob.storage_class, 'NEARLINE')
self.assertEqual(blob.size, SIZE)
self.assertEqual(blob.md5_hash, MD5_HASH)
self.assertEqual(blob.media_link, MEDIA_LINK)
self.assertEqual(blob.content_encoding, 'gzip')
self.assertEqual(blob.content_disposition, 'inline')
self.assertEqual(blob.content_language, 'en-US')
self.assertEqual(blob.cache_control, 'private')
self.assertEqual(blob.metadata, {'foo': 'Foo'})
self.assertEqual(blob.owner, {'entity': ENTITY, 'entityId': ENTITY_ID})
self.assertEqual(blob.crc32c, CRC32C)
self.assertEqual(blob.component_count, COMPONENT_COUNT)
self.assertEqual(blob.etag, ETAG)

if kms_key_name is not None:
self.assertEqual(blob.kms_key_name, kms_key_name)
else:
self.assertIsNone(blob.kms_key_name)

def test__set_properties_wo_kms_key_name(self):
self._set_properties_helper()

def test__set_properties_w_kms_key_name(self):
kms_resource = (
"projects/test-project-123/"
"locations/global/"
"keyRings/test-ring/"
"cryptoKeys/test-key/"
)
self._set_properties_helper(kms_key_name=kms_resource)

def test_chunk_size_ctor(self):
from google.cloud.storage.blob import Blob
Expand Down Expand Up @@ -2287,7 +2383,9 @@ def test_rewrite_same_name_w_old_key_new_kms_key(self):
self.assertEqual(kw[0]['path'], PATH)
self.assertEqual(kw[0]['query_params'],
{'destinationKmsKeyName': DEST_KMS_RESOURCE})
SENT = {}
SENT = {
'kmsKeyName': DEST_KMS_RESOURCE,
}
self.assertEqual(kw[0]['data'], SENT)

headers = {
Expand Down