Skip to content

Commit 19dd648

Browse files
Add cmek to spanner database (#4699) (#3181)
* Add cmek to spanner database * Update timeout * Bump default timeout * Mark test as beta * Move to handwritten test Signed-off-by: Modular Magician <[email protected]>
1 parent fc8333a commit 19dd648

File tree

6 files changed

+175
-2
lines changed

6 files changed

+175
-2
lines changed

.changelog/4699.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
```release-note:enhancement
2+
spanner: added support for setting a CMEK on `google_spanner_database`
3+
```
4+
```release-note:note
5+
all: changed default HTTP request timeout from 30 seconds to 120 seconds
6+
```

google-beta/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ func expandProviderBatchingConfig(v interface{}) (*batchingConfig, error) {
350350

351351
func (c *Config) synchronousTimeout() time.Duration {
352352
if c.RequestTimeout == 0 {
353-
return 30 * time.Second
353+
return 120 * time.Second
354354
}
355355
return c.RequestTimeout
356356
}

google-beta/resource_dataflow_flex_template_job_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88

99
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
1010
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
11-
compute "google.golang.org/api/compute/v1"
11+
"google.golang.org/api/compute/v1"
1212
)
1313

1414
func TestAccDataflowFlexTemplateJob_basic(t *testing.T) {

google-beta/resource_spanner_database.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,24 @@ error in any statement, the database is not created.`,
102102
Type: schema.TypeString,
103103
},
104104
},
105+
"encryption_config": {
106+
Type: schema.TypeList,
107+
Optional: true,
108+
ForceNew: true,
109+
Description: `Encryption configuration for the database`,
110+
MaxItems: 1,
111+
Elem: &schema.Resource{
112+
Schema: map[string]*schema.Schema{
113+
"kms_key_name": {
114+
Type: schema.TypeString,
115+
Required: true,
116+
ForceNew: true,
117+
Description: `Fully qualified name of the KMS key to use to encrypt this database. This key must exist
118+
in the same location as the Spanner Database.`,
119+
},
120+
},
121+
},
122+
},
105123
"state": {
106124
Type: schema.TypeString,
107125
Computed: true,
@@ -143,6 +161,12 @@ func resourceSpannerDatabaseCreate(d *schema.ResourceData, meta interface{}) err
143161
} else if v, ok := d.GetOkExists("ddl"); !isEmptyValue(reflect.ValueOf(extraStatementsProp)) && (ok || !reflect.DeepEqual(v, extraStatementsProp)) {
144162
obj["extraStatements"] = extraStatementsProp
145163
}
164+
encryptionConfigProp, err := expandSpannerDatabaseEncryptionConfig(d.Get("encryption_config"), d, config)
165+
if err != nil {
166+
return err
167+
} else if v, ok := d.GetOkExists("encryption_config"); !isEmptyValue(reflect.ValueOf(encryptionConfigProp)) && (ok || !reflect.DeepEqual(v, encryptionConfigProp)) {
168+
obj["encryptionConfig"] = encryptionConfigProp
169+
}
146170
instanceProp, err := expandSpannerDatabaseInstance(d.Get("instance"), d, config)
147171
if err != nil {
148172
return err
@@ -280,6 +304,9 @@ func resourceSpannerDatabaseRead(d *schema.ResourceData, meta interface{}) error
280304
if err := d.Set("state", flattenSpannerDatabaseState(res["state"], d, config)); err != nil {
281305
return fmt.Errorf("Error reading Database: %s", err)
282306
}
307+
if err := d.Set("encryption_config", flattenSpannerDatabaseEncryptionConfig(res["encryptionConfig"], d, config)); err != nil {
308+
return fmt.Errorf("Error reading Database: %s", err)
309+
}
283310
if err := d.Set("instance", flattenSpannerDatabaseInstance(res["instance"], d, config)); err != nil {
284311
return fmt.Errorf("Error reading Database: %s", err)
285312
}
@@ -434,6 +461,23 @@ func flattenSpannerDatabaseState(v interface{}, d *schema.ResourceData, config *
434461
return v
435462
}
436463

464+
func flattenSpannerDatabaseEncryptionConfig(v interface{}, d *schema.ResourceData, config *Config) interface{} {
465+
if v == nil {
466+
return nil
467+
}
468+
original := v.(map[string]interface{})
469+
if len(original) == 0 {
470+
return nil
471+
}
472+
transformed := make(map[string]interface{})
473+
transformed["kms_key_name"] =
474+
flattenSpannerDatabaseEncryptionConfigKmsKeyName(original["kmsKeyName"], d, config)
475+
return []interface{}{transformed}
476+
}
477+
func flattenSpannerDatabaseEncryptionConfigKmsKeyName(v interface{}, d *schema.ResourceData, config *Config) interface{} {
478+
return v
479+
}
480+
437481
func flattenSpannerDatabaseInstance(v interface{}, d *schema.ResourceData, config *Config) interface{} {
438482
if v == nil {
439483
return v
@@ -449,6 +493,29 @@ func expandSpannerDatabaseDdl(v interface{}, d TerraformResourceData, config *Co
449493
return v, nil
450494
}
451495

496+
func expandSpannerDatabaseEncryptionConfig(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
497+
l := v.([]interface{})
498+
if len(l) == 0 || l[0] == nil {
499+
return nil, nil
500+
}
501+
raw := l[0]
502+
original := raw.(map[string]interface{})
503+
transformed := make(map[string]interface{})
504+
505+
transformedKmsKeyName, err := expandSpannerDatabaseEncryptionConfigKmsKeyName(original["kms_key_name"], d, config)
506+
if err != nil {
507+
return nil, err
508+
} else if val := reflect.ValueOf(transformedKmsKeyName); val.IsValid() && !isEmptyValue(val) {
509+
transformed["kmsKeyName"] = transformedKmsKeyName
510+
}
511+
512+
return transformed, nil
513+
}
514+
515+
func expandSpannerDatabaseEncryptionConfigKmsKeyName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
516+
return v, nil
517+
}
518+
452519
func expandSpannerDatabaseInstance(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
453520
f, err := parseGlobalFieldValue("instances", v.(string), "project", d, config, true)
454521
if err != nil {

google-beta/resource_spanner_database_test.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,3 +257,91 @@ resource "google_spanner_database" "database" {
257257
}
258258
`, context)
259259
}
260+
261+
func TestAccSpannerDatabase_cmek(t *testing.T) {
262+
skipIfVcr(t)
263+
t.Parallel()
264+
265+
context := map[string]interface{}{
266+
"random_suffix": randString(t, 10),
267+
}
268+
269+
vcrTest(t, resource.TestCase{
270+
PreCheck: func() { testAccPreCheck(t) },
271+
Providers: testAccProvidersOiCS,
272+
CheckDestroy: testAccCheckSpannerDatabaseDestroyProducer(t),
273+
Steps: []resource.TestStep{
274+
{
275+
Config: testAccSpannerDatabase_cmek(context),
276+
},
277+
{
278+
ResourceName: "google_spanner_database.database",
279+
ImportState: true,
280+
ImportStateVerify: true,
281+
ImportStateVerifyIgnore: []string{"ddl", "deletion_protection"},
282+
},
283+
},
284+
})
285+
}
286+
287+
func testAccSpannerDatabase_cmek(context map[string]interface{}) string {
288+
return Nprintf(`
289+
resource "google_spanner_instance" "main" {
290+
provider = google-beta
291+
config = "regional-europe-west1"
292+
display_name = "main-instance1"
293+
}
294+
295+
resource "google_spanner_database" "database" {
296+
provider = google-beta
297+
instance = google_spanner_instance.main.name
298+
name = "tf-test-cmek-db%{random_suffix}"
299+
ddl = [
300+
"CREATE TABLE t1 (t1 INT64 NOT NULL,) PRIMARY KEY(t1)",
301+
"CREATE TABLE t2 (t2 INT64 NOT NULL,) PRIMARY KEY(t2)",
302+
]
303+
304+
encryption_config {
305+
kms_key_name = google_kms_crypto_key.example-key.id
306+
}
307+
308+
deletion_protection = false
309+
310+
depends_on = [google_kms_crypto_key_iam_binding.crypto-key-binding]
311+
}
312+
313+
resource "google_kms_key_ring" "keyring" {
314+
provider = google-beta
315+
name = "tf-test-ring%{random_suffix}"
316+
location = "europe-west1"
317+
}
318+
319+
resource "google_kms_crypto_key" "example-key" {
320+
provider = google-beta
321+
name = "tf-test-key%{random_suffix}"
322+
key_ring = google_kms_key_ring.keyring.id
323+
rotation_period = "100000s"
324+
}
325+
326+
resource "google_kms_crypto_key_iam_binding" "crypto-key-binding" {
327+
provider = google-beta
328+
crypto_key_id = google_kms_crypto_key.example-key.id
329+
role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
330+
331+
members = [
332+
"serviceAccount:${google_project_service_identity.ck_sa.email}",
333+
]
334+
}
335+
336+
data "google_project" "project" {
337+
provider = google-beta
338+
}
339+
340+
resource "google_project_service_identity" "ck_sa" {
341+
provider = google-beta
342+
project = data.google_project.project.project_id
343+
service = "spanner.googleapis.com"
344+
}
345+
346+
`, context)
347+
}

website/docs/r/spanner_database.html.markdown

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,25 @@ The following arguments are supported:
8888
execute atomically with the creation of the database: if there is an
8989
error in any statement, the database is not created.
9090

91+
* `encryption_config` -
92+
(Optional)
93+
Encryption configuration for the database
94+
Structure is documented below.
95+
9196
* `project` - (Optional) The ID of the project in which the resource belongs.
9297
If it is not provided, the provider project is used.
9398

9499
* `deletion_protection` - (Optional) Whether or not to allow Terraform to destroy the instance. Unless this field is set to false
95100
in Terraform state, a `terraform destroy` or `terraform apply` that would delete the instance will fail.
96101

97102

103+
The `encryption_config` block supports:
104+
105+
* `kms_key_name` -
106+
(Required)
107+
Fully qualified name of the KMS key to use to encrypt this database. This key must exist
108+
in the same location as the Spanner Database.
109+
98110
## Attributes Reference
99111

100112
In addition to the arguments listed above, the following computed attributes are exported:

0 commit comments

Comments
 (0)