Skip to content

Commit bb9f6b4

Browse files
committed
feat: add support for CMEK
1 parent 39a3264 commit bb9f6b4

File tree

6 files changed

+127
-4
lines changed

6 files changed

+127
-4
lines changed

google/cloud/spanner_v1/backup.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ def __init__(self, backup_id, instance, database="", expire_time=None):
6363
self._size_bytes = None
6464
self._state = None
6565
self._referencing_databases = None
66+
self._encryption_info = None
6667

6768
@property
6869
def name(self):
@@ -138,6 +139,15 @@ def referencing_databases(self):
138139
"""
139140
return self._referencing_databases
140141

142+
@property
143+
def encryption_info(self):
144+
"""Encryption info for this backup.
145+
146+
:rtype: :class:`~google.clod.spanner_admin_database_v1.proto.common_pb2.EncryptionInfo`
147+
:returns: a class representing the encryption info
148+
"""
149+
return self._encryption_info
150+
141151
@classmethod
142152
def from_pb(cls, backup_pb, instance):
143153
"""Create an instance of this class from a protobuf message.
@@ -232,6 +242,7 @@ def reload(self):
232242
self._size_bytes = pb.size_bytes
233243
self._state = enums.Backup.State(pb.state)
234244
self._referencing_databases = pb.referencing_databases
245+
self._encryption_info = pb.encryption_info
235246

236247
def update_expire_time(self, new_expire_time):
237248
"""Update the expire time of this backup.

google/cloud/spanner_v1/database.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
# pylint: disable=ungrouped-imports
3131
from google.cloud.spanner_admin_database_v1.gapic import enums
32+
from google.cloud.spanner_admin_database_v1.proto.common_pb2 import EncryptionConfig
3233
from google.cloud.spanner_v1._helpers import (
3334
_make_value_pb,
3435
_merge_query_options,
@@ -95,7 +96,14 @@ class Database(object):
9596

9697
_spanner_api = None
9798

98-
def __init__(self, database_id, instance, ddl_statements=(), pool=None):
99+
def __init__(
100+
self,
101+
database_id,
102+
instance,
103+
ddl_statements=(),
104+
pool=None,
105+
encryption_config=None,
106+
):
99107
self.database_id = database_id
100108
self._instance = instance
101109
self._ddl_statements = _check_ddl_statements(ddl_statements)
@@ -104,6 +112,13 @@ def __init__(self, database_id, instance, ddl_statements=(), pool=None):
104112
self._create_time = None
105113
self._restore_info = None
106114

115+
if type(encryption_config) == dict:
116+
self._encryption_config = EncryptionConfig(
117+
kms_key_name=encryption_config["kms_key_name"]
118+
)
119+
else:
120+
self._encryption_config = encryption_config
121+
107122
if pool is None:
108123
pool = BurstyPool()
109124

@@ -200,6 +215,15 @@ def restore_info(self):
200215
"""
201216
return self._restore_info
202217

218+
@property
219+
def encryption_config(self):
220+
"""Encryption config for this database.
221+
222+
:rtype: :class:`~google.cloud.spanner_admin_instance_v1.proto.common_pb2.EncryptionConfig`
223+
:returns: an object representing the restore info for this database
224+
"""
225+
return self._encryption_config
226+
203227
@property
204228
def ddl_statements(self):
205229
"""DDL Statements used to define database schema.
@@ -269,6 +293,7 @@ def create(self):
269293
parent=self._instance.name,
270294
create_statement="CREATE DATABASE %s" % (db_name,),
271295
extra_statements=list(self._ddl_statements),
296+
encryption_config=self._encryption_config,
272297
metadata=metadata,
273298
)
274299
return future
@@ -309,6 +334,7 @@ def reload(self):
309334
self._state = enums.Database.State(response.state)
310335
self._create_time = _pb_timestamp_to_datetime(response.create_time)
311336
self._restore_info = response.restore_info
337+
self._encryption_config = response.encryption_config
312338

313339
def update_ddl(self, ddl_statements, operation_id=""):
314340
"""Update DDL for this database.

google/cloud/spanner_v1/instance.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,9 @@ def delete(self):
348348

349349
api.delete_instance(self.name, metadata=metadata)
350350

351-
def database(self, database_id, ddl_statements=(), pool=None):
351+
def database(
352+
self, database_id, ddl_statements=(), pool=None, encryption_config=None
353+
):
352354
"""Factory to create a database within this instance.
353355
354356
:type database_id: str
@@ -365,7 +367,13 @@ def database(self, database_id, ddl_statements=(), pool=None):
365367
:rtype: :class:`~google.cloud.spanner_v1.database.Database`
366368
:returns: a database owned by this instance.
367369
"""
368-
return Database(database_id, self, ddl_statements=ddl_statements, pool=pool)
370+
return Database(
371+
database_id,
372+
self,
373+
ddl_statements=ddl_statements,
374+
pool=pool,
375+
encryption_config=encryption_config,
376+
)
369377

370378
def list_databases(self, page_size=None, page_token=None):
371379
"""List databases for the instance.

tests/unit/test_backup.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,18 @@ def test_referencing_databases_property(self):
170170
expected = backup._referencing_databases = [self.DATABASE_NAME]
171171
self.assertEqual(backup.referencing_databases, expected)
172172

173+
def test_encrpytion_info_property(self):
174+
from google.cloud.spanner_admin_database_v1.proto.common_pb2 import (
175+
EncryptionInfo,
176+
)
177+
178+
instance = _Instance(self.INSTANCE_NAME)
179+
backup = self._make_one(self.BACKUP_ID, instance)
180+
expected = backup._encryption_info = EncryptionInfo(
181+
kms_key_version="kms_key_version"
182+
)
183+
self.assertEqual(backup.encryption_info, expected)
184+
173185
def test_create_grpc_error(self):
174186
from google.api_core.exceptions import GoogleAPICallError
175187
from google.api_core.exceptions import Unknown
@@ -436,10 +448,14 @@ def test_reload_not_found(self):
436448

437449
def test_reload_success(self):
438450
from google.cloud.spanner_admin_database_v1.proto import backup_pb2
451+
from google.cloud.spanner_admin_database_v1.proto.common_pb2 import (
452+
EncryptionInfo,
453+
)
439454
from google.cloud.spanner_admin_database_v1.gapic import enums
440455
from google.cloud._helpers import _datetime_to_pb_timestamp
441456

442457
timestamp = self._make_timestamp()
458+
encryption_info = EncryptionInfo(kms_key_version="kms_key_version")
443459

444460
client = _Client()
445461
backup_pb = backup_pb2.Backup(
@@ -450,6 +466,7 @@ def test_reload_success(self):
450466
size_bytes=10,
451467
state=1,
452468
referencing_databases=[],
469+
encryption_info=encryption_info,
453470
)
454471
api = client.database_admin_api = self._make_database_admin_api()
455472
api.get_backup.return_value = backup_pb
@@ -464,6 +481,7 @@ def test_reload_success(self):
464481
self.assertEqual(backup.size_bytes, 10)
465482
self.assertEqual(backup.state, enums.Backup.State.CREATING)
466483
self.assertEqual(backup.referencing_databases, [])
484+
self.assertEqual(backup.encryption_info, encryption_info)
467485

468486
api.get_backup.assert_called_once_with(
469487
self.BACKUP_NAME, metadata=[("google-cloud-resource-prefix", backup.name)]

tests/unit/test_database.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,35 @@ def test_ctor_w_ddl_statements_ok(self):
146146
self.assertIs(database._instance, instance)
147147
self.assertEqual(list(database.ddl_statements), DDL_STATEMENTS)
148148

149+
def ctor_w_encryption_config(self):
150+
from google.cloud.spanner_admin_database_v1.proto.common_pb2 import (
151+
EncryptionConfig,
152+
)
153+
154+
instance = _Instance(self.INSTANCE_NAME)
155+
encryption_config = EncryptionConfig(kms_key_name="kms_key")
156+
database = self._make_one(
157+
self.DATABASE_ID, instance, encryption_config=encryption_config
158+
)
159+
self.assertEqual(database.database_id, self.DATABASE_ID)
160+
self.assertIs(database._instance, instance)
161+
self.assertEqual(database._encyption_config, encryption_config)
162+
163+
def ctor_w_encryption_config_dict(self):
164+
from google.cloud.spanner_admin_database_v1.proto.common_pb2 import (
165+
EncryptionConfig,
166+
)
167+
168+
instance = _Instance(self.INSTANCE_NAME)
169+
encryption_config_dict = {"kms_key_name": "kms_key"}
170+
encryption_config = EncryptionConfig(kms_key_name="kms_key")
171+
database = self._make_one(
172+
self.DATABASE_ID, instance, encryption_config=encryption_config_dict
173+
)
174+
self.assertEqual(database.database_id, self.DATABASE_ID)
175+
self.assertIs(database._instance, instance)
176+
self.assertEqual(database._encyption_config, encryption_config)
177+
149178
def test_from_pb_bad_database_name(self):
150179
from google.cloud.spanner_admin_database_v1.proto import (
151180
spanner_database_admin_pb2 as admin_v1_pb2,
@@ -260,6 +289,19 @@ def test_restore_info(self):
260289
)
261290
self.assertEqual(database.restore_info, restore_info)
262291

292+
def test_encryption_config(self):
293+
from google.cloud.spanner_admin_database_v1.proto.common_pb2 import (
294+
EncryptionConfig,
295+
)
296+
297+
instance = _Instance(self.INSTANCE_NAME)
298+
pool = _Pool()
299+
database = self._make_one(self.DATABASE_ID, instance, pool=pool)
300+
encryption_config = database._encryption_config = mock.create_autospec(
301+
EncryptionConfig, instance=True
302+
)
303+
self.assertEqual(database.encryption_config, encryption_config)
304+
263305
def test_spanner_api_property_w_scopeless_creds(self):
264306

265307
client = _Client()
@@ -396,6 +438,7 @@ def test_create_grpc_error(self):
396438
parent=self.INSTANCE_NAME,
397439
create_statement="CREATE DATABASE {}".format(self.DATABASE_ID),
398440
extra_statements=[],
441+
encryption_config=None,
399442
metadata=[("google-cloud-resource-prefix", database.name)],
400443
)
401444

@@ -417,6 +460,7 @@ def test_create_already_exists(self):
417460
parent=self.INSTANCE_NAME,
418461
create_statement="CREATE DATABASE `{}`".format(DATABASE_ID_HYPHEN),
419462
extra_statements=[],
463+
encryption_config=None,
420464
metadata=[("google-cloud-resource-prefix", database.name)],
421465
)
422466

@@ -437,6 +481,7 @@ def test_create_instance_not_found(self):
437481
parent=self.INSTANCE_NAME,
438482
create_statement="CREATE DATABASE {}".format(self.DATABASE_ID),
439483
extra_statements=[],
484+
encryption_config=None,
440485
metadata=[("google-cloud-resource-prefix", database.name)],
441486
)
442487

@@ -461,6 +506,7 @@ def test_create_success(self):
461506
parent=self.INSTANCE_NAME,
462507
create_statement="CREATE DATABASE {}".format(self.DATABASE_ID),
463508
extra_statements=DDL_STATEMENTS,
509+
encryption_config=None,
464510
metadata=[("google-cloud-resource-prefix", database.name)],
465511
)
466512

@@ -561,6 +607,9 @@ def test_reload_success(self):
561607
spanner_database_admin_pb2 as admin_v1_pb2,
562608
)
563609
from google.cloud.spanner_admin_database_v1.gapic import enums
610+
from google.cloud.spanner_admin_database_v1.proto.common_pb2 import (
611+
EncryptionConfig,
612+
)
564613
from google.cloud._helpers import _datetime_to_pb_timestamp
565614
from tests._fixtures import DDL_STATEMENTS
566615

@@ -569,12 +618,14 @@ def test_reload_success(self):
569618

570619
client = _Client()
571620
ddl_pb = admin_v1_pb2.GetDatabaseDdlResponse(statements=DDL_STATEMENTS)
621+
encryption_config = EncryptionConfig(kms_key_name="kms_key")
572622
api = client.database_admin_api = self._make_database_admin_api()
573623
api.get_database_ddl.return_value = ddl_pb
574624
db_pb = admin_v1_pb2.Database(
575625
state=2,
576626
create_time=_datetime_to_pb_timestamp(timestamp),
577627
restore_info=restore_info,
628+
encryption_config=encryption_config,
578629
)
579630
api.get_database.return_value = db_pb
580631
instance = _Instance(self.INSTANCE_NAME, client=client)
@@ -586,6 +637,7 @@ def test_reload_success(self):
586637
self.assertEqual(database._create_time, timestamp)
587638
self.assertEqual(database._restore_info, restore_info)
588639
self.assertEqual(database._ddl_statements, tuple(DDL_STATEMENTS))
640+
self.assertEqual(database._encryption_config, encryption_config)
589641

590642
api.get_database_ddl.assert_called_once_with(
591643
self.DATABASE_NAME,

tests/unit/test_instance.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,15 +479,22 @@ def test_database_factory_defaults(self):
479479

480480
def test_database_factory_explicit(self):
481481
from google.cloud.spanner_v1.database import Database
482+
from google.cloud.spanner_admin_database_v1.proto.common_pb2 import (
483+
EncryptionConfig,
484+
)
482485
from tests._fixtures import DDL_STATEMENTS
483486

484487
client = _Client(self.PROJECT)
485488
instance = self._make_one(self.INSTANCE_ID, client, self.CONFIG_NAME)
486489
DATABASE_ID = "database-id"
487490
pool = _Pool()
491+
encryption_config = EncryptionConfig(kms_key_name="kms_key")
488492

489493
database = instance.database(
490-
DATABASE_ID, ddl_statements=DDL_STATEMENTS, pool=pool
494+
DATABASE_ID,
495+
ddl_statements=DDL_STATEMENTS,
496+
pool=pool,
497+
encryption_config=encryption_config,
491498
)
492499

493500
self.assertIsInstance(database, Database)
@@ -496,6 +503,7 @@ def test_database_factory_explicit(self):
496503
self.assertEqual(list(database.ddl_statements), DDL_STATEMENTS)
497504
self.assertIs(database._pool, pool)
498505
self.assertIs(pool._bound, database)
506+
self.assertIs(database._encryption_config, encryption_config)
499507

500508
def test_list_databases(self):
501509
from google.cloud.spanner_admin_database_v1.gapic import database_admin_client

0 commit comments

Comments
 (0)