Skip to content

Commit 023b2e5

Browse files
committed
feat: add support for creating backups with CMEK
1 parent ebba31d commit 023b2e5

File tree

4 files changed

+133
-13
lines changed

4 files changed

+133
-13
lines changed

google/cloud/spanner_v1/backup.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
from google.cloud.exceptions import NotFound
2020

2121
from google.cloud.spanner_admin_database_v1 import Backup as BackupPB
22+
from google.cloud.spanner_admin_database_v1 import CreateBackupEncryptionConfig
23+
from google.cloud.spanner_admin_database_v1 import CreateBackupRequest
2224
from google.cloud.spanner_v1._helpers import _metadata_with_prefix
2325

2426
_BACKUP_NAME_RE = re.compile(
@@ -51,9 +53,17 @@ class Backup(object):
5153
:param expire_time: (Optional) The expire time that will be used to
5254
create the backup. Required if the create method
5355
needs to be called.
56+
57+
:type encryption_config:
58+
:class:`~google.cloud.spanner_admin_database_v1.types.CreateBackupEncryptionConfig`
59+
or :class:`dict`
60+
:param encryption_config:
61+
(Optional) Encryption configuration for the backup.
62+
If a dict is provided, it must be of the same form as the protobuf
63+
message :class:`~google.cloud.spanner_admin_database_v1.types.CreateBackupEncryptionConfig`
5464
"""
5565

56-
def __init__(self, backup_id, instance, database="", expire_time=None):
66+
def __init__(self, backup_id, instance, database="", expire_time=None, encryption_config=None):
5767
self.backup_id = backup_id
5868
self._instance = instance
5969
self._database = database
@@ -63,6 +73,10 @@ def __init__(self, backup_id, instance, database="", expire_time=None):
6373
self._state = None
6474
self._referencing_databases = None
6575
self._encryption_info = None
76+
if type(encryption_config) == dict:
77+
self._encryption_config = CreateBackupEncryptionConfig(**encryption_config)
78+
else:
79+
self._encryption_config = encryption_config
6680

6781
@property
6882
def name(self):
@@ -146,6 +160,14 @@ def encryption_info(self):
146160
"""
147161
return self._encryption_info
148162

163+
@property
164+
def encryption_config(self):
165+
"""Encryption config for this database.
166+
:rtype: :class:`~google.cloud.spanner_admin_instance_v1.CreateBackupEncryptionConfig`
167+
:returns: an object representing the restore info for this database
168+
"""
169+
return self._encryption_config
170+
149171
@classmethod
150172
def from_pb(cls, backup_pb, instance):
151173
"""Create an instance of this class from a protobuf message.
@@ -200,11 +222,15 @@ def create(self):
200222
api = self._instance._client.database_admin_api
201223
metadata = _metadata_with_prefix(self.name)
202224
backup = BackupPB(database=self._database, expire_time=self.expire_time,)
203-
204-
future = api.create_backup(
225+
request = CreateBackupRequest(
205226
parent=self._instance.name,
206227
backup_id=self.backup_id,
207228
backup=backup,
229+
encryption_config=self._encryption_config,
230+
)
231+
232+
future = api.create_backup(
233+
request=request,
208234
metadata=metadata,
209235
)
210236
return future

google/cloud/spanner_v1/instance.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ def list_databases(self, page_size=None):
415415
)
416416
return page_iter
417417

418-
def backup(self, backup_id, database="", expire_time=None):
418+
def backup(self, backup_id, database="", expire_time=None, encryption_config=None):
419419
"""Factory to create a backup within this instance.
420420
421421
:type backup_id: str
@@ -430,10 +430,18 @@ def backup(self, backup_id, database="", expire_time=None):
430430
:param expire_time:
431431
Optional. The expire time that will be used when creating the backup.
432432
Required if the create method needs to be called.
433+
434+
:type encryption_config:
435+
:class:`~google.cloud.spanner_admin_database_v1.types.CreateBackupEncryptionConfig`
436+
or :class:`dict`
437+
:param encryption_config:
438+
(Optional) Encryption configuration for the backup.
439+
If a dict is provided, it must be of the same form as the protobuf
440+
message :class:`~google.cloud.spanner_admin_database_v1.types.CreateBackupEncryptionConfig`
433441
"""
434442
try:
435443
return Backup(
436-
backup_id, self, database=database.name, expire_time=expire_time
444+
backup_id, self, database=database.name, expire_time=expire_time, encryption_config=encryption_config
437445
)
438446
except AttributeError:
439447
return Backup(backup_id, self, database=database, expire_time=expire_time)

tests/unit/test_backup.py

Lines changed: 87 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,53 @@ def test_ctor_defaults(self):
6262
self.assertIsNone(backup._expire_time)
6363

6464
def test_ctor_non_defaults(self):
65+
from google.cloud.spanner_admin_database_v1 import CreateBackupEncryptionConfig
6566
instance = _Instance(self.INSTANCE_NAME)
6667
timestamp = self._make_timestamp()
6768

69+
encryption_config = CreateBackupEncryptionConfig(
70+
encryption_type=CreateBackupEncryptionConfig.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION,
71+
kms_key_name="key_name"
72+
)
6873
backup = self._make_one(
69-
self.BACKUP_ID, instance, database=self.DATABASE_NAME, expire_time=timestamp
74+
self.BACKUP_ID,
75+
instance,
76+
database=self.DATABASE_NAME,
77+
expire_time=timestamp,
78+
encryption_config=encryption_config
7079
)
7180

7281
self.assertEqual(backup.backup_id, self.BACKUP_ID)
7382
self.assertIs(backup._instance, instance)
7483
self.assertEqual(backup._database, self.DATABASE_NAME)
7584
self.assertIsNotNone(backup._expire_time)
7685
self.assertIs(backup._expire_time, timestamp)
86+
self.assertEqual(backup.encryption_config, encryption_config)
87+
88+
def test_ctor_w_encryption_config_dict(self):
89+
from google.cloud.spanner_admin_database_v1 import CreateBackupEncryptionConfig
90+
instance = _Instance(self.INSTANCE_NAME)
91+
timestamp = self._make_timestamp()
92+
93+
encryption_config = {
94+
"encryption_type": 3,
95+
"kms_key_name": "key_name"
96+
}
97+
backup = self._make_one(
98+
self.BACKUP_ID,
99+
instance,
100+
database=self.DATABASE_NAME,
101+
expire_time=timestamp,
102+
encryption_config=encryption_config
103+
)
104+
expected_encryption_config = CreateBackupEncryptionConfig(**encryption_config)
105+
106+
self.assertEqual(backup.backup_id, self.BACKUP_ID)
107+
self.assertIs(backup._instance, instance)
108+
self.assertEqual(backup._database, self.DATABASE_NAME)
109+
self.assertIsNotNone(backup._expire_time)
110+
self.assertIs(backup._expire_time, timestamp)
111+
self.assertEqual(backup.encryption_config, expected_encryption_config)
77112

78113
def test_from_pb_project_mismatch(self):
79114
from google.cloud.spanner_admin_database_v1 import Backup
@@ -180,10 +215,22 @@ def test_encrpytion_info_property(self):
180215
)
181216
self.assertEqual(backup.encryption_info, expected)
182217

218+
def test_encryption_config_property(self):
219+
from google.cloud.spanner_admin_database_v1 import CreateBackupEncryptionConfig
220+
221+
instance = _Instance(self.INSTANCE_NAME)
222+
backup = self._make_one(self.BACKUP_ID, instance)
223+
expected = backup._encryption_config = CreateBackupEncryptionConfig(
224+
encryption_type=CreateBackupEncryptionConfig.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION,
225+
kms_key_name="kms_key_name"
226+
)
227+
self.assertEqual(backup.encryption_config, expected)
228+
183229
def test_create_grpc_error(self):
184230
from google.api_core.exceptions import GoogleAPICallError
185231
from google.api_core.exceptions import Unknown
186232
from google.cloud.spanner_admin_database_v1 import Backup
233+
from google.cloud.spanner_admin_database_v1 import CreateBackupRequest
187234

188235
client = _Client()
189236
api = client.database_admin_api = self._make_database_admin_api()
@@ -200,16 +247,22 @@ def test_create_grpc_error(self):
200247
with self.assertRaises(GoogleAPICallError):
201248
backup.create()
202249

203-
api.create_backup.assert_called_once_with(
250+
request = CreateBackupRequest(
204251
parent=self.INSTANCE_NAME,
205252
backup_id=self.BACKUP_ID,
206253
backup=backup_pb,
254+
)
255+
256+
api.create_backup.assert_called_once_with(
257+
request=request,
207258
metadata=[("google-cloud-resource-prefix", backup.name)],
208259
)
209260

261+
210262
def test_create_already_exists(self):
211263
from google.cloud.exceptions import Conflict
212264
from google.cloud.spanner_admin_database_v1 import Backup
265+
from google.cloud.spanner_admin_database_v1 import CreateBackupRequest
213266

214267
client = _Client()
215268
api = client.database_admin_api = self._make_database_admin_api()
@@ -226,16 +279,22 @@ def test_create_already_exists(self):
226279
with self.assertRaises(Conflict):
227280
backup.create()
228281

229-
api.create_backup.assert_called_once_with(
282+
request = CreateBackupRequest(
230283
parent=self.INSTANCE_NAME,
231284
backup_id=self.BACKUP_ID,
232285
backup=backup_pb,
286+
)
287+
288+
api.create_backup.assert_called_once_with(
289+
request=request,
233290
metadata=[("google-cloud-resource-prefix", backup.name)],
234291
)
235292

293+
236294
def test_create_instance_not_found(self):
237295
from google.cloud.exceptions import NotFound
238296
from google.cloud.spanner_admin_database_v1 import Backup
297+
from google.cloud.spanner_admin_database_v1 import CreateBackupRequest
239298

240299
client = _Client()
241300
api = client.database_admin_api = self._make_database_admin_api()
@@ -252,13 +311,18 @@ def test_create_instance_not_found(self):
252311
with self.assertRaises(NotFound):
253312
backup.create()
254313

255-
api.create_backup.assert_called_once_with(
314+
request = CreateBackupRequest(
256315
parent=self.INSTANCE_NAME,
257316
backup_id=self.BACKUP_ID,
258317
backup=backup_pb,
318+
)
319+
320+
api.create_backup.assert_called_once_with(
321+
request=request,
259322
metadata=[("google-cloud-resource-prefix", backup.name)],
260323
)
261324

325+
262326
def test_create_expire_time_not_set(self):
263327
instance = _Instance(self.INSTANCE_NAME)
264328
backup = self._make_one(self.BACKUP_ID, instance, database=self.DATABASE_NAME)
@@ -276,6 +340,8 @@ def test_create_database_not_set(self):
276340

277341
def test_create_success(self):
278342
from google.cloud.spanner_admin_database_v1 import Backup
343+
from google.cloud.spanner_admin_database_v1 import CreateBackupRequest
344+
from google.cloud.spanner_admin_database_v1 import CreateBackupEncryptionConfig
279345

280346
op_future = object()
281347
client = _Client()
@@ -284,19 +350,33 @@ def test_create_success(self):
284350

285351
instance = _Instance(self.INSTANCE_NAME, client=client)
286352
timestamp = self._make_timestamp()
353+
encryption_config = {
354+
"encryption_type": 3,
355+
"kms_key_name": "key_name"
356+
}
287357
backup = self._make_one(
288-
self.BACKUP_ID, instance, database=self.DATABASE_NAME, expire_time=timestamp
358+
self.BACKUP_ID,
359+
instance,
360+
database=self.DATABASE_NAME,
361+
expire_time=timestamp,
362+
encryption_config=encryption_config
289363
)
290364

291-
backup_pb = Backup(database=self.DATABASE_NAME, expire_time=timestamp,)
365+
backup_pb = Backup(database=self.DATABASE_NAME, expire_time=timestamp)
292366

293367
future = backup.create()
294368
self.assertIs(future, op_future)
295369

296-
api.create_backup.assert_called_once_with(
370+
expected_encryption_config = CreateBackupEncryptionConfig(**encryption_config)
371+
request = CreateBackupRequest(
297372
parent=self.INSTANCE_NAME,
298373
backup_id=self.BACKUP_ID,
299374
backup=backup_pb,
375+
encryption_config=expected_encryption_config
376+
)
377+
378+
api.create_backup.assert_called_once_with(
379+
request=request,
300380
metadata=[("google-cloud-resource-prefix", backup.name)],
301381
)
302382

tests/unit/test_instance.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -605,22 +605,28 @@ def test_backup_factory_explicit(self):
605605
import datetime
606606
from google.cloud._helpers import UTC
607607
from google.cloud.spanner_v1.backup import Backup
608+
from google.cloud.spanner_admin_database_v1 import CreateBackupEncryptionConfig
608609

609610
client = _Client(self.PROJECT)
610611
instance = self._make_one(self.INSTANCE_ID, client, self.CONFIG_NAME)
611612
BACKUP_ID = "backup-id"
612613
DATABASE_NAME = "database-name"
613614
timestamp = datetime.datetime.utcnow().replace(tzinfo=UTC)
615+
encryption_config = CreateBackupEncryptionConfig(
616+
encryption_type=CreateBackupEncryptionConfig.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION,
617+
kms_key_name="kms_key_name"
618+
)
614619

615620
backup = instance.backup(
616-
BACKUP_ID, database=DATABASE_NAME, expire_time=timestamp
621+
BACKUP_ID, database=DATABASE_NAME, expire_time=timestamp, encryption_config=encryption_config
617622
)
618623

619624
self.assertIsInstance(backup, Backup)
620625
self.assertEqual(backup.backup_id, BACKUP_ID)
621626
self.assertIs(backup._instance, instance)
622627
self.assertEqual(backup._database, DATABASE_NAME)
623628
self.assertIs(backup._expire_time, timestamp)
629+
self.assertEqual(backup._encryption_config, encryption_config)
624630

625631
def test_list_backups_defaults(self):
626632
from google.cloud.spanner_admin_database_v1 import Backup as BackupPB

0 commit comments

Comments
 (0)