Skip to content

Spanner MR CMEK Integration #8403

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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