Skip to content

Commit dfbe1cf

Browse files
panerorenn9541ScottSuarezmelinath
authored andcommitted
Spanner MR CMEK Integration (GoogleCloudPlatform#11319)
Co-authored-by: Scott Suarez <[email protected]> Co-authored-by: Stephen Lewis (Burrows) <[email protected]>
1 parent bfe7477 commit dfbe1cf

File tree

5 files changed

+170
-8
lines changed

5 files changed

+170
-8
lines changed

docs/content/develop/permadiff.md

+34-6
Original file line numberDiff line numberDiff line change
@@ -234,15 +234,29 @@ Add a [custom flattener]({{< ref "/develop/custom-code#custom_flatten" >}}) for
234234
235235
```go
236236
func flatten{{$.GetPrefix}}{{$.TitlelizeProperty}}(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
237-
configValue := d.Get("path.0.to.0.parent_field.0.nested_field").([]string)
237+
rawConfigValue := d.Get("path.0.to.0.parent_field.0.nested_field")
238238
239-
sorted, err := tpgresource.SortStringsByConfigOrder(configValue, v.([]string))
239+
// Convert config value to []string
240+
configValue, err := tpgresource.InterfaceSliceToStringSlice(rawConfigValue)
241+
if err != nil {
242+
log.Printf("[ERROR] Failed to convert config value: %s", err)
243+
return v
244+
}
245+
246+
// Convert v to []string
247+
apiStringValue, err := tpgresource.InterfaceSliceToStringSlice(v)
248+
if err != nil {
249+
log.Printf("[ERROR] Failed to convert API value: %s", err)
250+
return v
251+
}
252+
253+
sortedStrings, err := tpgresource.SortStringsByConfigOrder(configValue, apiStringValue)
240254
if err != nil {
241255
log.Printf("[ERROR] Could not sort API response value: %s", err)
242256
return v
243257
}
244258
245-
return sorted.(interface{})
259+
return sortedStrings
246260
}
247261
```
248262
{{< /tab >}}
@@ -251,15 +265,29 @@ Define resource-specific functions in your service package, for example at the t
251265

252266
```go
253267
func flattenResourceNameFieldName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
254-
configValue := d.Get("path.0.to.0.parent_field.0.nested_field").([]string)
268+
rawConfigValue := d.Get("path.0.to.0.parent_field.0.nested_field")
269+
270+
// Convert config value to []string
271+
configValue, err := tpgresource.InterfaceSliceToStringSlice(rawConfigValue)
272+
if err != nil {
273+
log.Printf("[ERROR] Failed to convert config value: %s", err)
274+
return v
275+
}
276+
277+
// Convert v to []string
278+
apiStringValue, err := tpgresource.InterfaceSliceToStringSlice(v)
279+
if err != nil {
280+
log.Printf("[ERROR] Failed to convert API value: %s", err)
281+
return v
282+
}
255283

256-
sorted, err := tpgresource.SortStringsByConfigOrder(configValue, v.([]string))
284+
sortedStrings, err := tpgresource.SortStringsByConfigOrder(configValue, apiStringValue)
257285
if err != nil {
258286
log.Printf("[ERROR] Could not sort API response value: %s", err)
259287
return v
260288
}
261289

262-
return sorted.(interface{})
290+
return sortedStrings
263291
}
264292
```
265293
{{< /tab >}}

mmv1/products/spanner/Database.yaml

+15-1
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,22 @@ properties:
150150
description: |
151151
Fully qualified name of the KMS key to use to encrypt this database. This key must exist
152152
in the same location as the Spanner Database.
153-
required: true
154153
immutable: true
154+
exactly_one_of:
155+
- encryption_config.0.kms_key_name
156+
- encryption_config.0.kms_key_names
157+
- name: 'kmsKeyNames'
158+
type: Array
159+
description: |
160+
Fully qualified name of the KMS keys to use to encrypt this database. The keys must exist
161+
in the same locations as the Spanner Database.
162+
immutable: true
163+
custom_flatten: templates/terraform/custom_flatten/spanner_database_kms_key_names.go.tmpl
164+
item_type:
165+
type: String
166+
exactly_one_of:
167+
- encryption_config.0.kms_key_name
168+
- encryption_config.0.kms_key_names
155169
- name: 'databaseDialect'
156170
type: Enum
157171
description: |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
func flatten{{$.GetPrefix}}{{$.TitlelizeProperty}}(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
2+
// Ignore `kms_key_names` if `kms_key_name` is set, because that field takes precedence.
3+
_, kmsNameSet := d.GetOk("encryption_config.0.kms_key_name")
4+
if kmsNameSet {
5+
return nil
6+
}
7+
8+
rawConfigValue := d.Get("encryption_config.0.kms_key_names")
9+
10+
// Convert config value to []string
11+
configValue, err := tpgresource.InterfaceSliceToStringSlice(rawConfigValue)
12+
if err != nil {
13+
log.Printf("[ERROR] Failed to convert config value: %s", err)
14+
return v
15+
}
16+
17+
// Convert v to []string
18+
apiStringValue, err := tpgresource.InterfaceSliceToStringSlice(v)
19+
if err != nil {
20+
log.Printf("[ERROR] Failed to convert API value: %s", err)
21+
return v
22+
}
23+
24+
sortedStrings, err := tpgresource.SortStringsByConfigOrder(configValue, apiStringValue)
25+
if err != nil {
26+
log.Printf("[ERROR] Could not sort API response value: %s", err)
27+
return v
28+
}
29+
30+
return sortedStrings
31+
}

mmv1/third_party/terraform/services/spanner/resource_spanner_database_test.go.tmpl

+71-1
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ func TestAccSpannerDatabase_cmek(t *testing.T) {
539539
ResourceName: "google_spanner_database.database",
540540
ImportState: true,
541541
ImportStateVerify: true,
542-
ImportStateVerifyIgnore: []string{"ddl", "deletion_protection"},
542+
ImportStateVerifyIgnore: []string{"ddl", "deletion_protection", "encryption_config.0.kms_key_names"},
543543
},
544544
},
545545
})
@@ -605,4 +605,74 @@ resource "google_project_service_identity" "ck_sa" {
605605

606606
`, context)
607607
}
608+
609+
func TestAccSpannerDatabase_mrcmek(t *testing.T) {
610+
acctest.SkipIfVcr(t)
611+
t.Parallel()
612+
613+
kms1 := acctest.BootstrapKMSKeyWithPurposeInLocationAndName(t, "ENCRYPT_DECRYPT", "us-central1", "tf-mr-cmek-test-key-us-central1")
614+
kms2 := acctest.BootstrapKMSKeyWithPurposeInLocationAndName(t, "ENCRYPT_DECRYPT", "us-east1", "tf-mr-cmek-test-key-us-east1")
615+
kms3 := acctest.BootstrapKMSKeyWithPurposeInLocationAndName(t, "ENCRYPT_DECRYPT", "us-east4", "tf-mr-cmek-test-key-us-east4")
616+
context := map[string]interface{}{
617+
"random_suffix": acctest.RandString(t, 10),
618+
"key_ring1": kms1.KeyRing.Name,
619+
"key_name1": kms1.CryptoKey.Name,
620+
"key_ring2": kms2.KeyRing.Name,
621+
"key_name2": kms2.CryptoKey.Name,
622+
"key_ring3": kms3.KeyRing.Name,
623+
"key_name3": kms3.CryptoKey.Name,
624+
}
625+
626+
acctest.VcrTest(t, resource.TestCase{
627+
PreCheck: func() { acctest.AccTestPreCheck(t) },
628+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t),
629+
CheckDestroy: testAccCheckSpannerDatabaseDestroyProducer(t),
630+
Steps: []resource.TestStep{
631+
{
632+
Config: testAccSpannerDatabase_mrcmek(context),
633+
},
634+
{
635+
ResourceName: "google_spanner_database.database",
636+
ImportState: true,
637+
ImportStateVerify: true,
638+
ImportStateVerifyIgnore: []string{"ddl", "deletion_protection"},
639+
},
640+
},
641+
})
642+
}
643+
644+
func testAccSpannerDatabase_mrcmek(context map[string]interface{}) string {
645+
return acctest.Nprintf(`
646+
resource "google_spanner_instance" "main" {
647+
provider = google-beta
648+
config = "nam3"
649+
display_name = "main-instance1"
650+
num_nodes = 1
651+
}
652+
653+
resource "google_spanner_database" "database" {
654+
provider = google-beta
655+
instance = google_spanner_instance.main.name
656+
name = "tf-test-mrcmek-db%{random_suffix}"
657+
ddl = [
658+
"CREATE TABLE t1 (t1 INT64 NOT NULL,) PRIMARY KEY(t1)",
659+
"CREATE TABLE t2 (t2 INT64 NOT NULL,) PRIMARY KEY(t2)",
660+
]
661+
662+
encryption_config {
663+
kms_key_names = [
664+
"%{key_name1}",
665+
"%{key_name2}",
666+
"%{key_name3}",
667+
]
668+
}
669+
670+
deletion_protection = false
671+
672+
}
673+
674+
675+
`, context)
676+
}
677+
608678
{{- end }}

mmv1/third_party/terraform/tpgresource/utils.go

+19
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,25 @@ func ExpandStringMap(d TerraformResourceData, key string) map[string]string {
238238
return ConvertStringMap(v.(map[string]interface{}))
239239
}
240240

241+
// InterfaceSliceToStringSlice converts a []interface{} containing strings to []string
242+
func InterfaceSliceToStringSlice(v interface{}) ([]string, error) {
243+
interfaceSlice, ok := v.([]interface{})
244+
if !ok {
245+
return nil, fmt.Errorf("expected []interface{}, got %T", v)
246+
}
247+
248+
stringSlice := make([]string, len(interfaceSlice))
249+
for i, item := range interfaceSlice {
250+
strItem, ok := item.(string)
251+
if !ok {
252+
return nil, fmt.Errorf("expected string, got %T at index %d", item, i)
253+
}
254+
stringSlice[i] = strItem
255+
}
256+
257+
return stringSlice, nil
258+
}
259+
241260
// SortStringsByConfigOrder takes a slice of map[string]interface{} from a TF config
242261
// and API data, and returns a new slice containing the API data, reorderd to match
243262
// the TF config as closely as possible (with new items at the end of the list.)

0 commit comments

Comments
 (0)