Skip to content

Commit 5b38c39

Browse files
Spanner MR CMEK Integration (#11319) (#8403)
[upstream:b0450ba0e17ac08ac7e07aa3bbb6eeea51785db4] Signed-off-by: Modular Magician <[email protected]>
1 parent 9f01612 commit 5b38c39

File tree

5 files changed

+156
-3
lines changed

5 files changed

+156
-3
lines changed

.changelog/11319.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
spanner: added `kmsKeyNames` to encryptionConfig of Database
3+
```

google-beta/services/spanner/resource_spanner_database.go

+58-1
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,22 @@ whereas setting “enableDropProtection” to true protects the database from de
162162
Schema: map[string]*schema.Schema{
163163
"kms_key_name": {
164164
Type: schema.TypeString,
165-
Required: true,
165+
Optional: true,
166166
ForceNew: true,
167167
Description: `Fully qualified name of the KMS key to use to encrypt this database. This key must exist
168168
in the same location as the Spanner Database.`,
169+
ExactlyOneOf: []string{"encryption_config.0.kms_key_name", "encryption_config.0.kms_key_names"},
170+
},
171+
"kms_key_names": {
172+
Type: schema.TypeList,
173+
Optional: true,
174+
ForceNew: true,
175+
Description: `Fully qualified name of the KMS keys to use to encrypt this database. The keys must exist
176+
in the same locations as the Spanner Database.`,
177+
Elem: &schema.Schema{
178+
Type: schema.TypeString,
179+
},
180+
ExactlyOneOf: []string{"encryption_config.0.kms_key_name", "encryption_config.0.kms_key_names"},
169181
},
170182
},
171183
},
@@ -821,12 +833,46 @@ func flattenSpannerDatabaseEncryptionConfig(v interface{}, d *schema.ResourceDat
821833
transformed := make(map[string]interface{})
822834
transformed["kms_key_name"] =
823835
flattenSpannerDatabaseEncryptionConfigKmsKeyName(original["kmsKeyName"], d, config)
836+
transformed["kms_key_names"] =
837+
flattenSpannerDatabaseEncryptionConfigKmsKeyNames(original["kmsKeyNames"], d, config)
824838
return []interface{}{transformed}
825839
}
826840
func flattenSpannerDatabaseEncryptionConfigKmsKeyName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
827841
return v
828842
}
829843

844+
func flattenSpannerDatabaseEncryptionConfigKmsKeyNames(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
845+
// Ignore `kms_key_names` if `kms_key_name` is set, because that field takes precedence.
846+
_, kmsNameSet := d.GetOk("encryption_config.0.kms_key_name")
847+
if kmsNameSet {
848+
return nil
849+
}
850+
851+
rawConfigValue := d.Get("encryption_config.0.kms_key_names")
852+
853+
// Convert config value to []string
854+
configValue, err := tpgresource.InterfaceSliceToStringSlice(rawConfigValue)
855+
if err != nil {
856+
log.Printf("[ERROR] Failed to convert config value: %s", err)
857+
return v
858+
}
859+
860+
// Convert v to []string
861+
apiStringValue, err := tpgresource.InterfaceSliceToStringSlice(v)
862+
if err != nil {
863+
log.Printf("[ERROR] Failed to convert API value: %s", err)
864+
return v
865+
}
866+
867+
sortedStrings, err := tpgresource.SortStringsByConfigOrder(configValue, apiStringValue)
868+
if err != nil {
869+
log.Printf("[ERROR] Could not sort API response value: %s", err)
870+
return v
871+
}
872+
873+
return sortedStrings
874+
}
875+
830876
func flattenSpannerDatabaseDatabaseDialect(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
831877
return v
832878
}
@@ -870,13 +916,24 @@ func expandSpannerDatabaseEncryptionConfig(v interface{}, d tpgresource.Terrafor
870916
transformed["kmsKeyName"] = transformedKmsKeyName
871917
}
872918

919+
transformedKmsKeyNames, err := expandSpannerDatabaseEncryptionConfigKmsKeyNames(original["kms_key_names"], d, config)
920+
if err != nil {
921+
return nil, err
922+
} else if val := reflect.ValueOf(transformedKmsKeyNames); val.IsValid() && !tpgresource.IsEmptyValue(val) {
923+
transformed["kmsKeyNames"] = transformedKmsKeyNames
924+
}
925+
873926
return transformed, nil
874927
}
875928

876929
func expandSpannerDatabaseEncryptionConfigKmsKeyName(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
877930
return v, nil
878931
}
879932

933+
func expandSpannerDatabaseEncryptionConfigKmsKeyNames(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
934+
return v, nil
935+
}
936+
880937
func expandSpannerDatabaseDatabaseDialect(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
881938
return v, nil
882939
}

google-beta/services/spanner/resource_spanner_database_test.go

+70-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
})
@@ -603,5 +603,74 @@ resource "google_project_service_identity" "ck_sa" {
603603
service = "spanner.googleapis.com"
604604
}
605605
606+
`, context)
607+
}
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+
606675
`, context)
607676
}

google-beta/tpgresource/utils.go

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

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

website/docs/r/spanner_database.html.markdown

+6-1
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,15 @@ When the field is set to false, deleting the database is allowed.
131131
<a name="nested_encryption_config"></a>The `encryption_config` block supports:
132132

133133
* `kms_key_name` -
134-
(Required)
134+
(Optional)
135135
Fully qualified name of the KMS key to use to encrypt this database. This key must exist
136136
in the same location as the Spanner Database.
137137

138+
* `kms_key_names` -
139+
(Optional)
140+
Fully qualified name of the KMS keys to use to encrypt this database. The keys must exist
141+
in the same locations as the Spanner Database.
142+
138143
## Attributes Reference
139144

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

0 commit comments

Comments
 (0)