Skip to content

Commit

Permalink
feat(spanner): support encryption config in google_spanner_schedule_b…
Browse files Browse the repository at this point in the history
…ackup resource (#12868) (#21067)

[upstream:fea6363d3402f501792c381c263af41aecd45ff6]

Signed-off-by: Modular Magician <magic-modules@google.com>
  • Loading branch information
modular-magician authored Jan 28, 2025
1 parent 0aee1bf commit 34754b4
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .changelog/12868.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
spanner: added `encryption_config` field to `google_spanner_backup_schedule`
```
102 changes: 102 additions & 0 deletions google/services/spanner/resource_spanner_backup_schedule.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,32 @@ func ResourceSpannerBackupSchedule() *schema.Resource {
A duration in seconds with up to nine fractional digits, ending with 's'. Example: '3.5s'.
You can set this to a value up to 366 days.`,
},
"encryption_config": {
Type: schema.TypeList,
Computed: true,
Optional: true,
Description: `Configuration for the encryption of the backup schedule.`,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"encryption_type": {
Type: schema.TypeString,
Required: true,
ValidateFunc: verify.ValidateEnum([]string{"USE_DATABASE_ENCRYPTION", "GOOGLE_DEFAULT_ENCRYPTION", "CUSTOMER_MANAGED_ENCRYPTION"}),
Description: `The encryption type of backups created by the backup schedule.
Possible values are USE_DATABASE_ENCRYPTION, GOOGLE_DEFAULT_ENCRYPTION, or CUSTOMER_MANAGED_ENCRYPTION.
If you use CUSTOMER_MANAGED_ENCRYPTION, you must specify a kmsKeyName.
If your backup type is incremental-backup, the encryption type must be GOOGLE_DEFAULT_ENCRYPTION. Possible values: ["USE_DATABASE_ENCRYPTION", "GOOGLE_DEFAULT_ENCRYPTION", "CUSTOMER_MANAGED_ENCRYPTION"]`,
},
"kms_key_name": {
Type: schema.TypeString,
Optional: true,
Description: `The resource name of the Cloud KMS key to use for encryption.
Format: 'projects/{project}/locations/{location}/keyRings/{keyRing}/cryptoKeys/{cryptoKey}'`,
},
},
},
},
"full_backup_spec": {
Type: schema.TypeList,
Optional: true,
Expand Down Expand Up @@ -191,6 +217,12 @@ func resourceSpannerBackupScheduleCreate(d *schema.ResourceData, meta interface{
} else if v, ok := d.GetOkExists("incremental_backup_spec"); ok || !reflect.DeepEqual(v, incrementalBackupSpecProp) {
obj["incrementalBackupSpec"] = incrementalBackupSpecProp
}
encryptionConfigProp, err := expandSpannerBackupScheduleEncryptionConfig(d.Get("encryption_config"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("encryption_config"); !tpgresource.IsEmptyValue(reflect.ValueOf(encryptionConfigProp)) && (ok || !reflect.DeepEqual(v, encryptionConfigProp)) {
obj["encryptionConfig"] = encryptionConfigProp
}

obj, err = resourceSpannerBackupScheduleEncoder(d, meta, obj)
if err != nil {
Expand Down Expand Up @@ -312,6 +344,9 @@ func resourceSpannerBackupScheduleRead(d *schema.ResourceData, meta interface{})
if err := d.Set("incremental_backup_spec", flattenSpannerBackupScheduleIncrementalBackupSpec(res["incrementalBackupSpec"], d, config)); err != nil {
return fmt.Errorf("Error reading BackupSchedule: %s", err)
}
if err := d.Set("encryption_config", flattenSpannerBackupScheduleEncryptionConfig(res["encryptionConfig"], d, config)); err != nil {
return fmt.Errorf("Error reading BackupSchedule: %s", err)
}

return nil
}
Expand Down Expand Up @@ -344,6 +379,12 @@ func resourceSpannerBackupScheduleUpdate(d *schema.ResourceData, meta interface{
} else if v, ok := d.GetOkExists("spec"); ok || !reflect.DeepEqual(v, specProp) {
obj["spec"] = specProp
}
encryptionConfigProp, err := expandSpannerBackupScheduleEncryptionConfig(d.Get("encryption_config"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("encryption_config"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, encryptionConfigProp)) {
obj["encryptionConfig"] = encryptionConfigProp
}

obj, err = resourceSpannerBackupScheduleEncoder(d, meta, obj)
if err != nil {
Expand All @@ -366,6 +407,10 @@ func resourceSpannerBackupScheduleUpdate(d *schema.ResourceData, meta interface{
if d.HasChange("spec") {
updateMask = append(updateMask, "spec")
}

if d.HasChange("encryption_config") {
updateMask = append(updateMask, "encryptionConfig")
}
// updateMask is a URL parameter but not present in the schema, so ReplaceVars
// won't set it
url, err = transport_tpg.AddQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")})
Expand Down Expand Up @@ -541,6 +586,29 @@ func flattenSpannerBackupScheduleIncrementalBackupSpec(v interface{}, d *schema.
return []interface{}{transformed}
}

func flattenSpannerBackupScheduleEncryptionConfig(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
if v == nil {
return nil
}
original := v.(map[string]interface{})
if len(original) == 0 {
return nil
}
transformed := make(map[string]interface{})
transformed["encryption_type"] =
flattenSpannerBackupScheduleEncryptionConfigEncryptionType(original["encryptionType"], d, config)
transformed["kms_key_name"] =
flattenSpannerBackupScheduleEncryptionConfigKmsKeyName(original["kmsKeyName"], d, config)
return []interface{}{transformed}
}
func flattenSpannerBackupScheduleEncryptionConfigEncryptionType(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
return v
}

func flattenSpannerBackupScheduleEncryptionConfigKmsKeyName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
return v
}

func expandSpannerBackupScheduleName(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
return v, nil
}
Expand Down Expand Up @@ -626,6 +694,40 @@ func expandSpannerBackupScheduleIncrementalBackupSpec(v interface{}, d tpgresour
return transformed, nil
}

func expandSpannerBackupScheduleEncryptionConfig(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
l := v.([]interface{})
if len(l) == 0 || l[0] == nil {
return nil, nil
}
raw := l[0]
original := raw.(map[string]interface{})
transformed := make(map[string]interface{})

transformedEncryptionType, err := expandSpannerBackupScheduleEncryptionConfigEncryptionType(original["encryption_type"], d, config)
if err != nil {
return nil, err
} else if val := reflect.ValueOf(transformedEncryptionType); val.IsValid() && !tpgresource.IsEmptyValue(val) {
transformed["encryptionType"] = transformedEncryptionType
}

transformedKmsKeyName, err := expandSpannerBackupScheduleEncryptionConfigKmsKeyName(original["kms_key_name"], d, config)
if err != nil {
return nil, err
} else if val := reflect.ValueOf(transformedKmsKeyName); val.IsValid() && !tpgresource.IsEmptyValue(val) {
transformed["kmsKeyName"] = transformedKmsKeyName
}

return transformed, nil
}

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

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

func resourceSpannerBackupScheduleEncoder(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) {
obj["name"] = d.Get("name").(string)
if obj["name"] == nil || obj["name"] == "" {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ api_resource_type_kind: 'BackupSchedule'
fields:
- field: 'database'
provider_only: true
- field: 'encryption_config.encryption_type'
- field: 'encryption_config.kms_key_name'
- field: 'full_backup_spec'
- field: 'incremental_backup_spec'
- field: 'instance'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ resource "google_spanner_backup_schedule" "full-backup" {
}
// The schedule creates only full backups.
full_backup_spec {}
encryption_config {
encryption_type = "USE_DATABASE_ENCRYPTION"
}
}
`, context)
}
Expand Down Expand Up @@ -169,6 +173,10 @@ resource "google_spanner_backup_schedule" "incremental-backup" {
}
// The schedule creates incremental backup chains.
incremental_backup_spec {}
encryption_config {
encryption_type = "GOOGLE_DEFAULT_ENCRYPTION"
}
}
`, context)
}
Expand Down
151 changes: 151 additions & 0 deletions google/services/spanner/resource_spanner_schedule_backup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,76 @@ func TestAccSpannerBackupSchedule_update(t *testing.T) {
})
}

func TestAccSpannerBackupSchedule_CMEKIncrementalBackup(t *testing.T) {
t.Parallel()
suffix := acctest.RandString(t, 10)
kms := acctest.BootstrapKMSKeyWithPurposeInLocationAndName(t, "ENCRYPT_DECRYPT", "us-central1", "tf-bootstrap-spanner-key")

context := map[string]interface{}{
"random_suffix": suffix,
"key_name": kms.CryptoKey.Name,
}

acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
CheckDestroy: testAccCheckSpannerBackupScheduleDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccSpannerBackupSchedule_CMEKIncremental(context),
},
{
ResourceName: "google_spanner_backup_schedule.backup_schedule",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccSpannerBackupSchedule_CMEKFullBackup(t *testing.T) {
t.Parallel()
suffix := acctest.RandString(t, 10)
kms := acctest.BootstrapKMSKeyWithPurposeInLocationAndName(t, "ENCRYPT_DECRYPT", "us-central1", "tf-bootstrap-spanner-key")

context := map[string]interface{}{
"random_suffix": suffix,
"key_name": kms.CryptoKey.Name,
}

acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
CheckDestroy: testAccCheckSpannerBackupScheduleDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccSpannerBackupSchedule_basic(context),
},
{
ResourceName: "google_spanner_backup_schedule.backup_schedule",
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccSpannerBackupSchedule_CMEKFull(context),
},
{
ResourceName: "google_spanner_backup_schedule.backup_schedule",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccSpannerBackupSchedule_basic(context map[string]interface{}) string {
return acctest.Nprintf(`
resource "google_spanner_instance" "instance" {
name = "my-instance-%{random_suffix}"
config = "regional-us-central1"
display_name = "My Instance"
num_nodes = 1
edition = "ENTERPRISE"
}
resource "google_spanner_database" "database" {
Expand Down Expand Up @@ -87,6 +150,7 @@ resource "google_spanner_instance" "instance" {
config = "regional-us-central1"
display_name = "My Instance"
num_nodes = 1
edition = "ENTERPRISE"
}
resource "google_spanner_database" "database" {
Expand Down Expand Up @@ -115,3 +179,90 @@ resource "google_spanner_backup_schedule" "backup_schedule" {
}
`, context)
}

func testAccSpannerBackupSchedule_CMEKIncremental(context map[string]interface{}) string {
return acctest.Nprintf(`
resource "google_spanner_instance" "instance" {
name = "my-instance-%{random_suffix}"
config = "regional-us-central1"
display_name = "My Instance"
num_nodes = 1
edition = "ENTERPRISE"
}
resource "google_spanner_database" "database" {
instance = google_spanner_instance.instance.name
name = "my-database-%{random_suffix}"
ddl = [
"CREATE TABLE t1 (t1 INT64 NOT NULL,) PRIMARY KEY(t1)",
]
deletion_protection = false
encryption_config {
kms_key_name = "%{key_name}"
}
}
resource "google_spanner_backup_schedule" "backup_schedule" {
instance = google_spanner_instance.instance.name
database = google_spanner_database.database.name
name = "my-backup-schedule-%{random_suffix}"
retention_duration = "172800s"
spec {
cron_spec {
text = "0 12 * * *"
}
}
incremental_backup_spec {}
encryption_config {
encryption_type = "GOOGLE_DEFAULT_ENCRYPTION"
}
}
`, context)
}

func testAccSpannerBackupSchedule_CMEKFull(context map[string]interface{}) string {
return acctest.Nprintf(`
resource "google_spanner_instance" "instance" {
name = "my-instance-%{random_suffix}"
config = "regional-us-central1"
display_name = "My Instance"
num_nodes = 1
edition = "ENTERPRISE"
}
resource "google_spanner_database" "database" {
instance = google_spanner_instance.instance.name
name = "my-database-%{random_suffix}"
ddl = [
"CREATE TABLE t1 (t1 INT64 NOT NULL,) PRIMARY KEY(t1)",
]
deletion_protection = false
}
resource "google_spanner_backup_schedule" "backup_schedule" {
instance = google_spanner_instance.instance.name
database = google_spanner_database.database.name
name = "my-backup-schedule-%{random_suffix}"
retention_duration = "172800s"
spec {
cron_spec {
text = "0 12 * * *"
}
}
full_backup_spec {}
encryption_config {
encryption_type = "CUSTOMER_MANAGED_ENCRYPTION"
kms_key_name = "%{key_name}"
}
}
`, context)
}
Loading

0 comments on commit 34754b4

Please sign in to comment.