Skip to content

Commit 8ef54dd

Browse files
Added support for CMEK in alloydb backup resource (#7829) (#14421)
* Added validation for "type" in cloud_sql_user_resource for preventing user from setting "password" or "host" for CLOUD_IAM_USER and CLOUD_IAM_SERVICE_ACCOUNT user types. * Removed validation and added documentation to prevent setting of host or password field for CLOUD_IAM_USER and CLOUD_IAM_SERVICE_ACCOUNT * Added support for CMEK in alloydb backup resource Signed-off-by: Modular Magician <[email protected]>
1 parent 527538b commit 8ef54dd

File tree

4 files changed

+248
-0
lines changed

4 files changed

+248
-0
lines changed

.changelog/7829.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
alloydb: added support for CMEK in `google_alloydb_backup` resource
3+
```

google/resource_alloydb_backup.go

+123
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,22 @@ func ResourceAlloydbBackup() *schema.Resource {
6868
ForceNew: true,
6969
Description: `User-provided description of the backup.`,
7070
},
71+
"encryption_config": {
72+
Type: schema.TypeList,
73+
Optional: true,
74+
Description: `EncryptionConfig describes the encryption config of a cluster or a backup that is encrypted with a CMEK (customer-managed encryption key).`,
75+
MaxItems: 1,
76+
Elem: &schema.Resource{
77+
Schema: map[string]*schema.Schema{
78+
"kms_key_name": {
79+
Type: schema.TypeString,
80+
Optional: true,
81+
ForceNew: true,
82+
Description: `The fully-qualified resource name of the KMS key. Each Cloud KMS key is regionalized and has the following format: projects/[PROJECT]/locations/[REGION]/keyRings/[RING]/cryptoKeys/[KEY_NAME].`,
83+
},
84+
},
85+
},
86+
},
7187
"labels": {
7288
Type: schema.TypeMap,
7389
Optional: true,
@@ -79,6 +95,28 @@ func ResourceAlloydbBackup() *schema.Resource {
7995
Computed: true,
8096
Description: `Time the Backup was created in UTC.`,
8197
},
98+
"encryption_info": {
99+
Type: schema.TypeList,
100+
Computed: true,
101+
Description: `EncryptionInfo describes the encryption information of a cluster or a backup.`,
102+
Elem: &schema.Resource{
103+
Schema: map[string]*schema.Schema{
104+
"encryption_type": {
105+
Type: schema.TypeString,
106+
Computed: true,
107+
Description: `Output only. Type of encryption.`,
108+
},
109+
"kms_key_versions": {
110+
Type: schema.TypeList,
111+
Computed: true,
112+
Description: `Output only. Cloud KMS key versions that are being used to protect the database or the backup.`,
113+
Elem: &schema.Schema{
114+
Type: schema.TypeString,
115+
},
116+
},
117+
},
118+
},
119+
},
82120
"etag": {
83121
Type: schema.TypeString,
84122
Computed: true,
@@ -146,6 +184,12 @@ func resourceAlloydbBackupCreate(d *schema.ResourceData, meta interface{}) error
146184
} else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(descriptionProp)) && (ok || !reflect.DeepEqual(v, descriptionProp)) {
147185
obj["description"] = descriptionProp
148186
}
187+
encryptionConfigProp, err := expandAlloydbBackupEncryptionConfig(d.Get("encryption_config"), d, config)
188+
if err != nil {
189+
return err
190+
} else if v, ok := d.GetOkExists("encryption_config"); !isEmptyValue(reflect.ValueOf(encryptionConfigProp)) && (ok || !reflect.DeepEqual(v, encryptionConfigProp)) {
191+
obj["encryptionConfig"] = encryptionConfigProp
192+
}
149193

150194
obj, err = resourceAlloydbBackupEncoder(d, meta, obj)
151195
if err != nil {
@@ -262,6 +306,12 @@ func resourceAlloydbBackupRead(d *schema.ResourceData, meta interface{}) error {
262306
if err := d.Set("etag", flattenAlloydbBackupEtag(res["etag"], d, config)); err != nil {
263307
return fmt.Errorf("Error reading Backup: %s", err)
264308
}
309+
if err := d.Set("encryption_config", flattenAlloydbBackupEncryptionConfig(res["encryptionConfig"], d, config)); err != nil {
310+
return fmt.Errorf("Error reading Backup: %s", err)
311+
}
312+
if err := d.Set("encryption_info", flattenAlloydbBackupEncryptionInfo(res["encryptionInfo"], d, config)); err != nil {
313+
return fmt.Errorf("Error reading Backup: %s", err)
314+
}
265315

266316
return nil
267317
}
@@ -288,6 +338,12 @@ func resourceAlloydbBackupUpdate(d *schema.ResourceData, meta interface{}) error
288338
} else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, labelsProp)) {
289339
obj["labels"] = labelsProp
290340
}
341+
encryptionConfigProp, err := expandAlloydbBackupEncryptionConfig(d.Get("encryption_config"), d, config)
342+
if err != nil {
343+
return err
344+
} else if v, ok := d.GetOkExists("encryption_config"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, encryptionConfigProp)) {
345+
obj["encryptionConfig"] = encryptionConfigProp
346+
}
291347

292348
obj, err = resourceAlloydbBackupEncoder(d, meta, obj)
293349
if err != nil {
@@ -305,6 +361,10 @@ func resourceAlloydbBackupUpdate(d *schema.ResourceData, meta interface{}) error
305361
if d.HasChange("labels") {
306362
updateMask = append(updateMask, "labels")
307363
}
364+
365+
if d.HasChange("encryption_config") {
366+
updateMask = append(updateMask, "encryptionConfig")
367+
}
308368
// updateMask is a URL parameter but not present in the schema, so ReplaceVars
309369
// won't set it
310370
url, err = AddQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")})
@@ -441,6 +501,46 @@ func flattenAlloydbBackupEtag(v interface{}, d *schema.ResourceData, config *tra
441501
return v
442502
}
443503

504+
func flattenAlloydbBackupEncryptionConfig(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
505+
if v == nil {
506+
return nil
507+
}
508+
original := v.(map[string]interface{})
509+
if len(original) == 0 {
510+
return nil
511+
}
512+
transformed := make(map[string]interface{})
513+
transformed["kms_key_name"] =
514+
flattenAlloydbBackupEncryptionConfigKmsKeyName(original["kmsKeyName"], d, config)
515+
return []interface{}{transformed}
516+
}
517+
func flattenAlloydbBackupEncryptionConfigKmsKeyName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
518+
return v
519+
}
520+
521+
func flattenAlloydbBackupEncryptionInfo(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
522+
if v == nil {
523+
return nil
524+
}
525+
original := v.(map[string]interface{})
526+
if len(original) == 0 {
527+
return nil
528+
}
529+
transformed := make(map[string]interface{})
530+
transformed["encryption_type"] =
531+
flattenAlloydbBackupEncryptionInfoEncryptionType(original["encryptionType"], d, config)
532+
transformed["kms_key_versions"] =
533+
flattenAlloydbBackupEncryptionInfoKmsKeyVersions(original["kmsKeyVersions"], d, config)
534+
return []interface{}{transformed}
535+
}
536+
func flattenAlloydbBackupEncryptionInfoEncryptionType(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
537+
return v
538+
}
539+
540+
func flattenAlloydbBackupEncryptionInfoKmsKeyVersions(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
541+
return v
542+
}
543+
444544
func expandAlloydbBackupClusterName(v interface{}, d TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
445545
return v, nil
446546
}
@@ -460,6 +560,29 @@ func expandAlloydbBackupDescription(v interface{}, d TerraformResourceData, conf
460560
return v, nil
461561
}
462562

563+
func expandAlloydbBackupEncryptionConfig(v interface{}, d TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
564+
l := v.([]interface{})
565+
if len(l) == 0 || l[0] == nil {
566+
return nil, nil
567+
}
568+
raw := l[0]
569+
original := raw.(map[string]interface{})
570+
transformed := make(map[string]interface{})
571+
572+
transformedKmsKeyName, err := expandAlloydbBackupEncryptionConfigKmsKeyName(original["kms_key_name"], d, config)
573+
if err != nil {
574+
return nil, err
575+
} else if val := reflect.ValueOf(transformedKmsKeyName); val.IsValid() && !isEmptyValue(val) {
576+
transformed["kmsKeyName"] = transformedKmsKeyName
577+
}
578+
579+
return transformed, nil
580+
}
581+
582+
func expandAlloydbBackupEncryptionConfigKmsKeyName(v interface{}, d TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
583+
return v, nil
584+
}
585+
463586
func resourceAlloydbBackupEncoder(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) {
464587
// The only other available type is AUTOMATED which cannot be set manually
465588
obj["type"] = "ON_DEMAND"

google/resource_alloydb_backup_test.go

+97
Original file line numberDiff line numberDiff line change
@@ -232,3 +232,100 @@ resource "google_service_networking_connection" "vpc_connection" {
232232
}
233233
`, context)
234234
}
235+
236+
func TestAccAlloydbBackup_usingCMEK(t *testing.T) {
237+
t.Parallel()
238+
239+
context := map[string]interface{}{
240+
"network_name": BootstrapSharedTestNetwork(t, "alloydb-update"),
241+
"random_suffix": RandString(t, 10),
242+
"key_name": "tf-test-key-" + RandString(t, 10),
243+
}
244+
245+
VcrTest(t, resource.TestCase{
246+
PreCheck: func() { AccTestPreCheck(t) },
247+
ProtoV5ProviderFactories: ProtoV5ProviderFactories(t),
248+
CheckDestroy: testAccCheckAlloydbBackupDestroyProducer(t),
249+
Steps: []resource.TestStep{
250+
{
251+
Config: testAccAlloydbBackup_usingCMEK(context),
252+
},
253+
{
254+
ResourceName: "google_alloydb_backup.default",
255+
ImportState: true,
256+
ImportStateVerify: true,
257+
ImportStateVerifyIgnore: []string{"backup_id", "location", "reconciling", "update_time"},
258+
},
259+
},
260+
})
261+
}
262+
263+
func testAccAlloydbBackup_usingCMEK(context map[string]interface{}) string {
264+
return Nprintf(`
265+
resource "google_alloydb_backup" "default" {
266+
location = "us-central1"
267+
backup_id = "tf-test-alloydb-backup%{random_suffix}"
268+
cluster_name = google_alloydb_cluster.default.name
269+
description = "example description"
270+
labels = {
271+
"label" = "updated_key"
272+
"label2" = "updated_key2"
273+
}
274+
encryption_config {
275+
kms_key_name = google_kms_crypto_key.key.id
276+
}
277+
depends_on = [google_alloydb_instance.default]
278+
}
279+
280+
resource "google_alloydb_cluster" "default" {
281+
cluster_id = "tf-test-alloydb-cluster%{random_suffix}"
282+
location = "us-central1"
283+
network = data.google_compute_network.default.id
284+
}
285+
286+
resource "google_alloydb_instance" "default" {
287+
cluster = google_alloydb_cluster.default.name
288+
instance_id = "tf-test-alloydb-instance%{random_suffix}"
289+
instance_type = "PRIMARY"
290+
291+
depends_on = [google_service_networking_connection.vpc_connection]
292+
}
293+
294+
resource "google_compute_global_address" "private_ip_alloc" {
295+
name = "tf-test-alloydb-cluster%{random_suffix}"
296+
address_type = "INTERNAL"
297+
purpose = "VPC_PEERING"
298+
prefix_length = 16
299+
network = data.google_compute_network.default.id
300+
}
301+
302+
resource "google_service_networking_connection" "vpc_connection" {
303+
network = data.google_compute_network.default.id
304+
service = "servicenetworking.googleapis.com"
305+
reserved_peering_ranges = [google_compute_global_address.private_ip_alloc.name]
306+
}
307+
308+
data "google_compute_network" "default" {
309+
name = "%{network_name}"
310+
}
311+
data "google_project" "project" {}
312+
313+
resource "google_kms_key_ring" "keyring" {
314+
name = "%{key_name}"
315+
location = "us-central1"
316+
}
317+
318+
resource "google_kms_crypto_key" "key" {
319+
name = "%{key_name}"
320+
key_ring = google_kms_key_ring.keyring.id
321+
}
322+
323+
resource "google_kms_crypto_key_iam_binding" "crypto_key" {
324+
crypto_key_id = google_kms_crypto_key.key.id
325+
role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
326+
members = [
327+
"serviceAccount:service-${data.google_project.project.number}@gcp-sa-alloydb.iam.gserviceaccount.com",
328+
]
329+
}
330+
`, context)
331+
}

website/docs/r/alloydb_backup.html.markdown

+25
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,21 @@ The following arguments are supported:
160160
(Optional)
161161
User-provided description of the backup.
162162

163+
* `encryption_config` -
164+
(Optional)
165+
EncryptionConfig describes the encryption config of a cluster or a backup that is encrypted with a CMEK (customer-managed encryption key).
166+
Structure is [documented below](#nested_encryption_config).
167+
163168
* `project` - (Optional) The ID of the project in which the resource belongs.
164169
If it is not provided, the provider project is used.
165170

166171

172+
<a name="nested_encryption_config"></a>The `encryption_config` block supports:
173+
174+
* `kms_key_name` -
175+
(Optional)
176+
The fully-qualified resource name of the KMS key. Each Cloud KMS key is regionalized and has the following format: projects/[PROJECT]/locations/[REGION]/keyRings/[RING]/cryptoKeys/[KEY_NAME].
177+
167178
## Attributes Reference
168179

169180
In addition to the arguments listed above, the following computed attributes are exported:
@@ -191,6 +202,20 @@ In addition to the arguments listed above, the following computed attributes are
191202
* `etag` -
192203
A hash of the resource.
193204

205+
* `encryption_info` -
206+
EncryptionInfo describes the encryption information of a cluster or a backup.
207+
Structure is [documented below](#nested_encryption_info).
208+
209+
210+
<a name="nested_encryption_info"></a>The `encryption_info` block contains:
211+
212+
* `encryption_type` -
213+
(Output)
214+
Output only. Type of encryption.
215+
216+
* `kms_key_versions` -
217+
(Output)
218+
Output only. Cloud KMS key versions that are being used to protect the database or the backup.
194219

195220
## Timeouts
196221

0 commit comments

Comments
 (0)