Skip to content

Commit

Permalink
Spanner MR CMEK Integration (#11319) (#8403)
Browse files Browse the repository at this point in the history
[upstream:b0450ba0e17ac08ac7e07aa3bbb6eeea51785db4]

Signed-off-by: Modular Magician <[email protected]>
  • Loading branch information
modular-magician authored Oct 14, 2024
1 parent 9f01612 commit 5b38c39
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 3 deletions.
3 changes: 3 additions & 0 deletions .changelog/11319.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
spanner: added `kmsKeyNames` to encryptionConfig of Database
```
59 changes: 58 additions & 1 deletion google-beta/services/spanner/resource_spanner_database.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,22 @@ whereas setting “enableDropProtection” to true protects the database from de
Schema: map[string]*schema.Schema{
"kms_key_name": {
Type: schema.TypeString,
Required: true,
Optional: true,
ForceNew: true,
Description: `Fully qualified name of the KMS key to use to encrypt this database. This key must exist
in the same location as the Spanner Database.`,
ExactlyOneOf: []string{"encryption_config.0.kms_key_name", "encryption_config.0.kms_key_names"},
},
"kms_key_names": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Description: `Fully qualified name of the KMS keys to use to encrypt this database. The keys must exist
in the same locations as the Spanner Database.`,
Elem: &schema.Schema{
Type: schema.TypeString,
},
ExactlyOneOf: []string{"encryption_config.0.kms_key_name", "encryption_config.0.kms_key_names"},
},
},
},
Expand Down Expand Up @@ -821,12 +833,46 @@ func flattenSpannerDatabaseEncryptionConfig(v interface{}, d *schema.ResourceDat
transformed := make(map[string]interface{})
transformed["kms_key_name"] =
flattenSpannerDatabaseEncryptionConfigKmsKeyName(original["kmsKeyName"], d, config)
transformed["kms_key_names"] =
flattenSpannerDatabaseEncryptionConfigKmsKeyNames(original["kmsKeyNames"], d, config)
return []interface{}{transformed}
}
func flattenSpannerDatabaseEncryptionConfigKmsKeyName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
return v
}

func flattenSpannerDatabaseEncryptionConfigKmsKeyNames(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
// Ignore `kms_key_names` if `kms_key_name` is set, because that field takes precedence.
_, kmsNameSet := d.GetOk("encryption_config.0.kms_key_name")
if kmsNameSet {
return nil
}

rawConfigValue := d.Get("encryption_config.0.kms_key_names")

// Convert config value to []string
configValue, err := tpgresource.InterfaceSliceToStringSlice(rawConfigValue)
if err != nil {
log.Printf("[ERROR] Failed to convert config value: %s", err)
return v
}

// Convert v to []string
apiStringValue, err := tpgresource.InterfaceSliceToStringSlice(v)
if err != nil {
log.Printf("[ERROR] Failed to convert API value: %s", err)
return v
}

sortedStrings, err := tpgresource.SortStringsByConfigOrder(configValue, apiStringValue)
if err != nil {
log.Printf("[ERROR] Could not sort API response value: %s", err)
return v
}

return sortedStrings
}

func flattenSpannerDatabaseDatabaseDialect(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
return v
}
Expand Down Expand Up @@ -870,13 +916,24 @@ func expandSpannerDatabaseEncryptionConfig(v interface{}, d tpgresource.Terrafor
transformed["kmsKeyName"] = transformedKmsKeyName
}

transformedKmsKeyNames, err := expandSpannerDatabaseEncryptionConfigKmsKeyNames(original["kms_key_names"], d, config)
if err != nil {
return nil, err
} else if val := reflect.ValueOf(transformedKmsKeyNames); val.IsValid() && !tpgresource.IsEmptyValue(val) {
transformed["kmsKeyNames"] = transformedKmsKeyNames
}

return transformed, nil
}

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

func expandSpannerDatabaseEncryptionConfigKmsKeyNames(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
return v, nil
}

func expandSpannerDatabaseDatabaseDialect(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
return v, nil
}
Expand Down
71 changes: 70 additions & 1 deletion google-beta/services/spanner/resource_spanner_database_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ func TestAccSpannerDatabase_cmek(t *testing.T) {
ResourceName: "google_spanner_database.database",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"ddl", "deletion_protection"},
ImportStateVerifyIgnore: []string{"ddl", "deletion_protection", "encryption_config.0.kms_key_names"},
},
},
})
Expand Down Expand Up @@ -603,5 +603,74 @@ resource "google_project_service_identity" "ck_sa" {
service = "spanner.googleapis.com"
}
`, context)
}

func TestAccSpannerDatabase_mrcmek(t *testing.T) {
acctest.SkipIfVcr(t)
t.Parallel()

kms1 := acctest.BootstrapKMSKeyWithPurposeInLocationAndName(t, "ENCRYPT_DECRYPT", "us-central1", "tf-mr-cmek-test-key-us-central1")
kms2 := acctest.BootstrapKMSKeyWithPurposeInLocationAndName(t, "ENCRYPT_DECRYPT", "us-east1", "tf-mr-cmek-test-key-us-east1")
kms3 := acctest.BootstrapKMSKeyWithPurposeInLocationAndName(t, "ENCRYPT_DECRYPT", "us-east4", "tf-mr-cmek-test-key-us-east4")
context := map[string]interface{}{
"random_suffix": acctest.RandString(t, 10),
"key_ring1": kms1.KeyRing.Name,
"key_name1": kms1.CryptoKey.Name,
"key_ring2": kms2.KeyRing.Name,
"key_name2": kms2.CryptoKey.Name,
"key_ring3": kms3.KeyRing.Name,
"key_name3": kms3.CryptoKey.Name,
}

acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t),
CheckDestroy: testAccCheckSpannerDatabaseDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccSpannerDatabase_mrcmek(context),
},
{
ResourceName: "google_spanner_database.database",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"ddl", "deletion_protection"},
},
},
})
}

func testAccSpannerDatabase_mrcmek(context map[string]interface{}) string {
return acctest.Nprintf(`
resource "google_spanner_instance" "main" {
provider = google-beta
config = "nam3"
display_name = "main-instance1"
num_nodes = 1
}
resource "google_spanner_database" "database" {
provider = google-beta
instance = google_spanner_instance.main.name
name = "tf-test-mrcmek-db%{random_suffix}"
ddl = [
"CREATE TABLE t1 (t1 INT64 NOT NULL,) PRIMARY KEY(t1)",
"CREATE TABLE t2 (t2 INT64 NOT NULL,) PRIMARY KEY(t2)",
]
encryption_config {
kms_key_names = [
"%{key_name1}",
"%{key_name2}",
"%{key_name3}",
]
}
deletion_protection = false
}
`, context)
}
19 changes: 19 additions & 0 deletions google-beta/tpgresource/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,25 @@ func ExpandStringMap(d TerraformResourceData, key string) map[string]string {
return ConvertStringMap(v.(map[string]interface{}))
}

// InterfaceSliceToStringSlice converts a []interface{} containing strings to []string
func InterfaceSliceToStringSlice(v interface{}) ([]string, error) {
interfaceSlice, ok := v.([]interface{})
if !ok {
return nil, fmt.Errorf("expected []interface{}, got %T", v)
}

stringSlice := make([]string, len(interfaceSlice))
for i, item := range interfaceSlice {
strItem, ok := item.(string)
if !ok {
return nil, fmt.Errorf("expected string, got %T at index %d", item, i)
}
stringSlice[i] = strItem
}

return stringSlice, nil
}

// SortStringsByConfigOrder takes a slice of map[string]interface{} from a TF config
// and API data, and returns a new slice containing the API data, reorderd to match
// the TF config as closely as possible (with new items at the end of the list.)
Expand Down
7 changes: 6 additions & 1 deletion website/docs/r/spanner_database.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,15 @@ When the field is set to false, deleting the database is allowed.
<a name="nested_encryption_config"></a>The `encryption_config` block supports:

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

* `kms_key_names` -
(Optional)
Fully qualified name of the KMS keys to use to encrypt this database. The keys must exist
in the same locations as the Spanner Database.

## Attributes Reference

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

0 comments on commit 5b38c39

Please sign in to comment.