Skip to content

Commit 337f097

Browse files
committed
feat: add support for creating databases with CMEK
1 parent 4626282 commit 337f097

File tree

6 files changed

+125
-4
lines changed

6 files changed

+125
-4
lines changed

google/cloud/spanner_v1/backup.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def __init__(
7171
self._size_bytes = None
7272
self._state = None
7373
self._referencing_databases = None
74+
self._encryption_info = None
7475

7576
@property
7677
def name(self):
@@ -156,6 +157,14 @@ def referencing_databases(self):
156157
"""
157158
return self._referencing_databases
158159

160+
@property
161+
def encryption_info(self):
162+
"""Encryption info for this backup.
163+
:rtype: :class:`~google.clod.spanner_admin_database_v1.proto.common_pb2.EncryptionInfo`
164+
:returns: a class representing the encryption info
165+
"""
166+
return self._encryption_info
167+
159168
@classmethod
160169
def from_pb(cls, backup_pb, instance):
161170
"""Create an instance of this class from a protobuf message.
@@ -255,6 +264,7 @@ def reload(self):
255264
self._size_bytes = pb.size_bytes
256265
self._state = BackupPB.State(pb.state)
257266
self._referencing_databases = pb.referencing_databases
267+
self._encryption_info = pb.encryption_info
258268

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

google/cloud/spanner_v1/database.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import copy
1818
import functools
1919
import grpc
20-
import logging
2120
import re
2221
import threading
2322

@@ -47,6 +46,7 @@
4746
SpannerGrpcTransport,
4847
)
4948
from google.cloud.spanner_admin_database_v1 import CreateDatabaseRequest
49+
from google.cloud.spanner_admin_database_v1 import EncryptionConfig
5050
from google.cloud.spanner_admin_database_v1 import UpdateDatabaseDdlRequest
5151
from google.cloud.spanner_v1 import ExecuteSqlRequest
5252
from google.cloud.spanner_v1 import (
@@ -102,12 +102,25 @@ class Database(object):
102102
is `True` to log commit statistics. If not passed, a logger
103103
will be created when needed that will log the commit statistics
104104
to stdout.
105+
:type encryption_config:
106+
:class:`~google.cloud.spanner_admin_database_v1.types.EncryptionConfig`
107+
or :class:`dict`
108+
:param encryption_config:
109+
(Optional) Encryption information about the database.
110+
If a dict is provided, it must be of the same form as the protobuf
111+
message :class:`~google.cloud.spanner_admin_database_v1.types.EncryptionConfig`
105112
"""
106113

107114
_spanner_api = None
108115

109116
def __init__(
110-
self, database_id, instance, ddl_statements=(), pool=None, logger=None
117+
self,
118+
database_id,
119+
instance,
120+
ddl_statements=(),
121+
pool=None,
122+
logger=None,
123+
encryption_config=None,
111124
):
112125
self.database_id = database_id
113126
self._instance = instance
@@ -121,6 +134,13 @@ def __init__(
121134
self.log_commit_stats = False
122135
self._logger = logger
123136

137+
if type(encryption_config) == dict:
138+
self._encryption_config = EncryptionConfig(
139+
kms_key_name=encryption_config["kms_key_name"]
140+
)
141+
else:
142+
self._encryption_config = encryption_config
143+
124144
if pool is None:
125145
pool = BurstyPool()
126146

@@ -236,6 +256,14 @@ def earliest_version_time(self):
236256
"""
237257
return self._earliest_version_time
238258

259+
@property
260+
def encryption_config(self):
261+
"""Encryption config for this database.
262+
:rtype: :class:`~google.cloud.spanner_admin_instance_v1.proto.common_pb2.EncryptionConfig`
263+
:returns: an object representing the restore info for this database
264+
"""
265+
return self._encryption_config
266+
239267
@property
240268
def ddl_statements(self):
241269
"""DDL Statements used to define database schema.
@@ -324,6 +352,7 @@ def create(self):
324352
parent=self._instance.name,
325353
create_statement="CREATE DATABASE %s" % (db_name,),
326354
extra_statements=list(self._ddl_statements),
355+
encryption_config=self._encryption_config,
327356
)
328357
future = api.create_database(request=request, metadata=metadata)
329358
return future
@@ -366,6 +395,7 @@ def reload(self):
366395
self._restore_info = response.restore_info
367396
self._version_retention_period = response.version_retention_period
368397
self._earliest_version_time = response.earliest_version_time
398+
self._encryption_config = response.encryption_config
369399

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

google/cloud/spanner_v1/instance.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ def delete(self):
357357

358358
api.delete_instance(name=self.name, metadata=metadata)
359359

360-
def database(self, database_id, ddl_statements=(), pool=None, logger=None):
360+
def database(self, database_id, ddl_statements=(), pool=None, logger=None, encryption_config=None):
361361
"""Factory to create a database within this instance.
362362
363363
:type database_id: str
@@ -377,12 +377,28 @@ def database(self, database_id, ddl_statements=(), pool=None, logger=None):
377377
will be created when needed that will log the commit statistics
378378
to stdout.
379379
380+
:type encryption_config:
381+
:class:`~google.cloud.spanner_admin_database_v1.types.EncryptionConfig`
382+
or :class:`dict`
383+
:param encryption_config:
384+
(Optional) Encryption information about the database.
385+
If a dict is provided, it must be of the same form as the protobuf
386+
message :class:`~google.cloud.spanner_admin_database_v1.types.EncryptionConfig
387+
380388
:rtype: :class:`~google.cloud.spanner_v1.database.Database`
381389
:returns: a database owned by this instance.
382390
"""
383391
return Database(
384392
database_id, self, ddl_statements=ddl_statements, pool=pool, logger=logger
385393
)
394+
return Database(
395+
database_id,
396+
self,
397+
ddl_statements=ddl_statements,
398+
pool=pool,
399+
logger=logger,
400+
encryption_config=encryption_config,
401+
)
386402

387403
def list_databases(self, page_size=None):
388404
"""List databases for the instance.

tests/unit/test_backup.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,16 @@ 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 import EncryptionInfo
175+
176+
instance = _Instance(self.INSTANCE_NAME)
177+
backup = self._make_one(self.BACKUP_ID, instance)
178+
expected = backup._encryption_info = EncryptionInfo(
179+
kms_key_version="kms_key_version"
180+
)
181+
self.assertEqual(backup.encryption_info, expected)
182+
173183
def test_create_grpc_error(self):
174184
from google.api_core.exceptions import GoogleAPICallError
175185
from google.api_core.exceptions import Unknown
@@ -442,8 +452,10 @@ def test_reload_not_found(self):
442452

443453
def test_reload_success(self):
444454
from google.cloud.spanner_admin_database_v1 import Backup
455+
from google.cloud.spanner_admin_database_v1 import EncryptionInfo
445456

446457
timestamp = self._make_timestamp()
458+
encryption_info = EncryptionInfo(kms_key_version="kms_key_version")
447459

448460
client = _Client()
449461
backup_pb = Backup(
@@ -455,6 +467,7 @@ def test_reload_success(self):
455467
size_bytes=10,
456468
state=1,
457469
referencing_databases=[],
470+
encryption_info=encryption_info,
458471
)
459472
api = client.database_admin_api = self._make_database_admin_api()
460473
api.get_backup.return_value = backup_pb
@@ -470,6 +483,7 @@ def test_reload_success(self):
470483
self.assertEqual(backup.size_bytes, 10)
471484
self.assertEqual(backup.state, Backup.State.CREATING)
472485
self.assertEqual(backup.referencing_databases, [])
486+
self.assertEqual(backup.encryption_info, encryption_info)
473487

474488
api.get_backup.assert_called_once_with(
475489
name=self.BACKUP_NAME,

tests/unit/test_database.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,31 @@ def test_ctor_w_explicit_logger(self):
159159
self.assertFalse(database.log_commit_stats)
160160
self.assertEqual(database._logger, logger)
161161

162+
def test_ctor_w_encryption_config(self):
163+
from google.cloud.spanner_admin_database_v1 import EncryptionConfig
164+
165+
instance = _Instance(self.INSTANCE_NAME)
166+
encryption_config = EncryptionConfig(kms_key_name="kms_key")
167+
database = self._make_one(
168+
self.DATABASE_ID, instance, encryption_config=encryption_config
169+
)
170+
self.assertEqual(database.database_id, self.DATABASE_ID)
171+
self.assertIs(database._instance, instance)
172+
self.assertEqual(database._encryption_config, encryption_config)
173+
174+
def test_ctor_w_encryption_config_dict(self):
175+
from google.cloud.spanner_admin_database_v1 import EncryptionConfig
176+
177+
instance = _Instance(self.INSTANCE_NAME)
178+
encryption_config_dict = {"kms_key_name": "kms_key"}
179+
encryption_config = EncryptionConfig(kms_key_name="kms_key")
180+
database = self._make_one(
181+
self.DATABASE_ID, instance, encryption_config=encryption_config_dict
182+
)
183+
self.assertEqual(database.database_id, self.DATABASE_ID)
184+
self.assertIs(database._instance, instance)
185+
self.assertEqual(database._encryption_config, encryption_config)
186+
162187
def test_from_pb_bad_database_name(self):
163188
from google.cloud.spanner_admin_database_v1 import Database
164189

@@ -295,6 +320,17 @@ def test_logger_property_custom(self):
295320
logger = database._logger = mock.create_autospec(logging.Logger, instance=True)
296321
self.assertEqual(database.logger, logger)
297322

323+
def test_encryption_config(self):
324+
from google.cloud.spanner_admin_database_v1 import EncryptionConfig
325+
326+
instance = _Instance(self.INSTANCE_NAME)
327+
pool = _Pool()
328+
database = self._make_one(self.DATABASE_ID, instance, pool=pool)
329+
encryption_config = database._encryption_config = mock.create_autospec(
330+
EncryptionConfig, instance=True
331+
)
332+
self.assertEqual(database.encryption_config, encryption_config)
333+
298334
def test_spanner_api_property_w_scopeless_creds(self):
299335

300336
client = _Client()
@@ -432,6 +468,7 @@ def test_create_grpc_error(self):
432468
parent=self.INSTANCE_NAME,
433469
create_statement="CREATE DATABASE {}".format(self.DATABASE_ID),
434470
extra_statements=[],
471+
encryption_config=None,
435472
)
436473

437474
api.create_database.assert_called_once_with(
@@ -458,6 +495,7 @@ def test_create_already_exists(self):
458495
parent=self.INSTANCE_NAME,
459496
create_statement="CREATE DATABASE `{}`".format(DATABASE_ID_HYPHEN),
460497
extra_statements=[],
498+
encryption_config=None,
461499
)
462500

463501
api.create_database.assert_called_once_with(
@@ -483,6 +521,7 @@ def test_create_instance_not_found(self):
483521
parent=self.INSTANCE_NAME,
484522
create_statement="CREATE DATABASE {}".format(self.DATABASE_ID),
485523
extra_statements=[],
524+
encryption_config=None,
486525
)
487526

488527
api.create_database.assert_called_once_with(
@@ -512,6 +551,7 @@ def test_create_success(self):
512551
parent=self.INSTANCE_NAME,
513552
create_statement="CREATE DATABASE {}".format(self.DATABASE_ID),
514553
extra_statements=DDL_STATEMENTS,
554+
encryption_config=None,
515555
)
516556

517557
api.create_database.assert_called_once_with(
@@ -611,6 +651,7 @@ def test_reload_not_found(self):
611651

612652
def test_reload_success(self):
613653
from google.cloud.spanner_admin_database_v1 import Database
654+
from google.cloud.spanner_admin_database_v1 import EncryptionConfig
614655
from google.cloud.spanner_admin_database_v1 import GetDatabaseDdlResponse
615656
from google.cloud.spanner_admin_database_v1 import RestoreInfo
616657
from google.cloud._helpers import _datetime_to_pb_timestamp
@@ -621,6 +662,7 @@ def test_reload_success(self):
621662

622663
client = _Client()
623664
ddl_pb = GetDatabaseDdlResponse(statements=DDL_STATEMENTS)
665+
encryption_config = EncryptionConfig(kms_key_name="kms_key")
624666
api = client.database_admin_api = self._make_database_admin_api()
625667
api.get_database_ddl.return_value = ddl_pb
626668
db_pb = Database(
@@ -629,6 +671,7 @@ def test_reload_success(self):
629671
restore_info=restore_info,
630672
version_retention_period="1d",
631673
earliest_version_time=_datetime_to_pb_timestamp(timestamp),
674+
encryption_config=encryption_config,
632675
)
633676
api.get_database.return_value = db_pb
634677
instance = _Instance(self.INSTANCE_NAME, client=client)
@@ -642,6 +685,7 @@ def test_reload_success(self):
642685
self.assertEqual(database._version_retention_period, "1d")
643686
self.assertEqual(database._earliest_version_time, timestamp)
644687
self.assertEqual(database._ddl_statements, tuple(DDL_STATEMENTS))
688+
self.assertEqual(database._encryption_config, encryption_config)
645689

646690
api.get_database_ddl.assert_called_once_with(
647691
database=self.DATABASE_NAME,

tests/unit/test_instance.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,7 @@ def test_database_factory_defaults(self):
490490

491491
def test_database_factory_explicit(self):
492492
from logging import Logger
493+
from google.cloud.spanner_admin_database_v1 import EncryptionConfig
493494
from google.cloud.spanner_v1.database import Database
494495
from tests._fixtures import DDL_STATEMENTS
495496

@@ -498,9 +499,14 @@ def test_database_factory_explicit(self):
498499
DATABASE_ID = "database-id"
499500
pool = _Pool()
500501
logger = mock.create_autospec(Logger, instance=True)
502+
encryption_config = EncryptionConfig(kms_key_name="kms_key")
501503

502504
database = instance.database(
503-
DATABASE_ID, ddl_statements=DDL_STATEMENTS, pool=pool, logger=logger
505+
DATABASE_ID,
506+
ddl_statements=DDL_STATEMENTS,
507+
pool=pool,
508+
logger=logger,
509+
encryption_config=encryption_config,
504510
)
505511

506512
self.assertIsInstance(database, Database)
@@ -510,6 +516,7 @@ def test_database_factory_explicit(self):
510516
self.assertIs(database._pool, pool)
511517
self.assertIs(database._logger, logger)
512518
self.assertIs(pool._bound, database)
519+
self.assertIs(database._encryption_config, encryption_config)
513520

514521
def test_list_databases(self):
515522
from google.cloud.spanner_admin_database_v1 import Database as DatabasePB

0 commit comments

Comments
 (0)