diff --git a/.changelog/3896.txt b/.changelog/3896.txt new file mode 100644 index 0000000000..ea5b1793de --- /dev/null +++ b/.changelog/3896.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +compute: added storage_locations field to `google_compute_snapshot` +``` diff --git a/google-beta/resource_compute_disk.go b/google-beta/resource_compute_disk.go index 63f5e2bdb0..8161db677f 100644 --- a/google-beta/resource_compute_disk.go +++ b/google-beta/resource_compute_disk.go @@ -292,7 +292,7 @@ See https://cloud.google.com/compute/docs/disks/customer-managed-encryption#encr Type: schema.TypeString, Optional: true, ForceNew: true, - Description: `The service account used for the encryption request for the given KMS key. + Description: `The service account used for the encryption request for the given KMS key. If absent, the Compute Engine Service Agent service account is used.`, }, "raw_key": { @@ -421,7 +421,7 @@ See https://cloud.google.com/compute/docs/disks/customer-managed-encryption#encr Type: schema.TypeString, Optional: true, ForceNew: true, - Description: `The service account used for the encryption request for the given KMS key. + Description: `The service account used for the encryption request for the given KMS key. If absent, the Compute Engine Service Agent service account is used.`, }, "raw_key": { @@ -1551,6 +1551,10 @@ func resourceComputeDiskDecoder(d *schema.ResourceData, meta interface{}, res ma transformed["kmsKeyName"] = strings.Split(kmsKeyName.(string), "/cryptoKeyVersions")[0] } + if kmsKeyServiceAccount, ok := original["kmsKeyServiceAccount"]; ok { + transformed["kmsKeyServiceAccount"] = kmsKeyServiceAccount + } + res["diskEncryptionKey"] = transformed } @@ -1567,6 +1571,10 @@ func resourceComputeDiskDecoder(d *schema.ResourceData, meta interface{}, res ma transformed["kmsKeyName"] = strings.Split(kmsKeyName.(string), "/cryptoKeyVersions")[0] } + if kmsKeyServiceAccount, ok := original["kmsKeyServiceAccount"]; ok { + transformed["kmsKeyServiceAccount"] = kmsKeyServiceAccount + } + res["sourceImageEncryptionKey"] = transformed } @@ -1583,6 +1591,10 @@ func resourceComputeDiskDecoder(d *schema.ResourceData, meta interface{}, res ma transformed["kmsKeyName"] = strings.Split(kmsKeyName.(string), "/cryptoKeyVersions")[0] } + if kmsKeyServiceAccount, ok := original["kmsKeyServiceAccount"]; ok { + transformed["kmsKeyServiceAccount"] = kmsKeyServiceAccount + } + res["sourceSnapshotEncryptionKey"] = transformed } diff --git a/google-beta/resource_compute_region_disk.go b/google-beta/resource_compute_region_disk.go index a8c3bb473c..4c2e5613a7 100644 --- a/google-beta/resource_compute_region_disk.go +++ b/google-beta/resource_compute_region_disk.go @@ -1080,6 +1080,10 @@ func resourceComputeRegionDiskDecoder(d *schema.ResourceData, meta interface{}, transformed["kmsKeyName"] = strings.Split(kmsKeyName.(string), "/cryptoKeyVersions")[0] } + if kmsKeyServiceAccount, ok := original["kmsKeyServiceAccount"]; ok { + transformed["kmsKeyServiceAccount"] = kmsKeyServiceAccount + } + res["diskEncryptionKey"] = transformed } @@ -1096,6 +1100,10 @@ func resourceComputeRegionDiskDecoder(d *schema.ResourceData, meta interface{}, transformed["kmsKeyName"] = strings.Split(kmsKeyName.(string), "/cryptoKeyVersions")[0] } + if kmsKeyServiceAccount, ok := original["kmsKeyServiceAccount"]; ok { + transformed["kmsKeyServiceAccount"] = kmsKeyServiceAccount + } + res["sourceImageEncryptionKey"] = transformed } @@ -1112,6 +1120,10 @@ func resourceComputeRegionDiskDecoder(d *schema.ResourceData, meta interface{}, transformed["kmsKeyName"] = strings.Split(kmsKeyName.(string), "/cryptoKeyVersions")[0] } + if kmsKeyServiceAccount, ok := original["kmsKeyServiceAccount"]; ok { + transformed["kmsKeyServiceAccount"] = kmsKeyServiceAccount + } + res["sourceSnapshotEncryptionKey"] = transformed } diff --git a/google-beta/resource_compute_snapshot.go b/google-beta/resource_compute_snapshot.go index 4848c46df4..4c205b85d9 100644 --- a/google-beta/resource_compute_snapshot.go +++ b/google-beta/resource_compute_snapshot.go @@ -19,6 +19,7 @@ import ( "log" "reflect" "strconv" + "strings" "time" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" @@ -82,9 +83,22 @@ source snapshot is protected by a customer-supplied encryption key.`, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "kms_key_self_link": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `The name of the encryption key that is stored in Google Cloud KMS.`, + }, + "kms_key_service_account": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `The service account used for the encryption request for the given KMS key. +If absent, the Compute Engine Service Agent service account is used.`, + }, "raw_key": { Type: schema.TypeString, - Required: true, + Optional: true, ForceNew: true, Description: `Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 base64 to either encrypt or decrypt this resource.`, @@ -109,6 +123,13 @@ key.`, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "kms_key_service_account": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `The service account used for the encryption request for the given KMS key. +If absent, the Compute Engine Service Agent service account is used.`, + }, "raw_key": { Type: schema.TypeString, Optional: true, @@ -120,6 +141,16 @@ RFC 4648 base64 to either encrypt or decrypt this resource.`, }, }, }, + "storage_locations": { + Type: schema.TypeList, + Computed: true, + Optional: true, + ForceNew: true, + Description: `Cloud Storage bucket storage location of the snapshot (regional or multi-regional).`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, "zone": { Type: schema.TypeString, Computed: true, @@ -202,6 +233,12 @@ func resourceComputeSnapshotCreate(d *schema.ResourceData, meta interface{}) err } else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(descriptionProp)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { obj["description"] = descriptionProp } + storageLocationsProp, err := expandComputeSnapshotStorageLocations(d.Get("storage_locations"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("storage_locations"); !isEmptyValue(reflect.ValueOf(storageLocationsProp)) && (ok || !reflect.DeepEqual(v, storageLocationsProp)) { + obj["storageLocations"] = storageLocationsProp + } labelsProp, err := expandComputeSnapshotLabels(d.Get("labels"), d, config) if err != nil { return err @@ -345,6 +382,9 @@ func resourceComputeSnapshotRead(d *schema.ResourceData, meta interface{}) error if err := d.Set("storage_bytes", flattenComputeSnapshotStorageBytes(res["storageBytes"], d, config)); err != nil { return fmt.Errorf("Error reading Snapshot: %s", err) } + if err := d.Set("storage_locations", flattenComputeSnapshotStorageLocations(res["storageLocations"], d, config)); err != nil { + return fmt.Errorf("Error reading Snapshot: %s", err) + } if err := d.Set("licenses", flattenComputeSnapshotLicenses(res["licenses"], d, config)); err != nil { return fmt.Errorf("Error reading Snapshot: %s", err) } @@ -553,6 +593,10 @@ func flattenComputeSnapshotStorageBytes(v interface{}, d *schema.ResourceData, c return v // let terraform core handle it otherwise } +func flattenComputeSnapshotStorageLocations(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + func flattenComputeSnapshotLicenses(v interface{}, d *schema.ResourceData, config *Config) interface{} { if v == nil { return v @@ -588,6 +632,10 @@ func flattenComputeSnapshotSnapshotEncryptionKey(v interface{}, d *schema.Resour flattenComputeSnapshotSnapshotEncryptionKeyRawKey(original["rawKey"], d, config) transformed["sha256"] = flattenComputeSnapshotSnapshotEncryptionKeySha256(original["sha256"], d, config) + transformed["kms_key_self_link"] = + flattenComputeSnapshotSnapshotEncryptionKeyKmsKeySelfLink(original["kmsKeyName"], d, config) + transformed["kms_key_service_account"] = + flattenComputeSnapshotSnapshotEncryptionKeyKmsKeyServiceAccount(original["kmsKeyServiceAccount"], d, config) return []interface{}{transformed} } func flattenComputeSnapshotSnapshotEncryptionKeyRawKey(v interface{}, d *schema.ResourceData, config *Config) interface{} { @@ -598,6 +646,14 @@ func flattenComputeSnapshotSnapshotEncryptionKeySha256(v interface{}, d *schema. return v } +func flattenComputeSnapshotSnapshotEncryptionKeyKmsKeySelfLink(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenComputeSnapshotSnapshotEncryptionKeyKmsKeyServiceAccount(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + func expandComputeSnapshotName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { return v, nil } @@ -606,6 +662,10 @@ func expandComputeSnapshotDescription(v interface{}, d TerraformResourceData, co return v, nil } +func expandComputeSnapshotStorageLocations(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + func expandComputeSnapshotLabels(v interface{}, d TerraformResourceData, config *Config) (map[string]string, error) { if v == nil { return map[string]string{}, nil @@ -660,6 +720,20 @@ func expandComputeSnapshotSnapshotEncryptionKey(v interface{}, d TerraformResour transformed["sha256"] = transformedSha256 } + transformedKmsKeySelfLink, err := expandComputeSnapshotSnapshotEncryptionKeyKmsKeySelfLink(original["kms_key_self_link"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedKmsKeySelfLink); val.IsValid() && !isEmptyValue(val) { + transformed["kmsKeyName"] = transformedKmsKeySelfLink + } + + transformedKmsKeyServiceAccount, err := expandComputeSnapshotSnapshotEncryptionKeyKmsKeyServiceAccount(original["kms_key_service_account"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedKmsKeyServiceAccount); val.IsValid() && !isEmptyValue(val) { + transformed["kmsKeyServiceAccount"] = transformedKmsKeyServiceAccount + } + return transformed, nil } @@ -671,6 +745,14 @@ func expandComputeSnapshotSnapshotEncryptionKeySha256(v interface{}, d Terraform return v, nil } +func expandComputeSnapshotSnapshotEncryptionKeyKmsKeySelfLink(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandComputeSnapshotSnapshotEncryptionKeyKmsKeyServiceAccount(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + func expandComputeSnapshotSourceDiskEncryptionKey(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { l := v.([]interface{}) if len(l) == 0 || l[0] == nil { @@ -687,6 +769,13 @@ func expandComputeSnapshotSourceDiskEncryptionKey(v interface{}, d TerraformReso transformed["rawKey"] = transformedRawKey } + transformedKmsKeyServiceAccount, err := expandComputeSnapshotSourceDiskEncryptionKeyKmsKeyServiceAccount(original["kms_key_service_account"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedKmsKeyServiceAccount); val.IsValid() && !isEmptyValue(val) { + transformed["kmsKeyServiceAccount"] = transformedKmsKeyServiceAccount + } + return transformed, nil } @@ -694,7 +783,51 @@ func expandComputeSnapshotSourceDiskEncryptionKeyRawKey(v interface{}, d Terrafo return v, nil } +func expandComputeSnapshotSourceDiskEncryptionKeyKmsKeyServiceAccount(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + func resourceComputeSnapshotDecoder(d *schema.ResourceData, meta interface{}, res map[string]interface{}) (map[string]interface{}, error) { + if v, ok := res["snapshotEncryptionKey"]; ok { + original := v.(map[string]interface{}) + transformed := make(map[string]interface{}) + // The raw key won't be returned, so we need to use the original. + transformed["rawKey"] = d.Get("snapshot_encryption_key.0.raw_key") + transformed["sha256"] = original["sha256"] + + if kmsKeyName, ok := original["kmsKeyName"]; ok { + // The response for crypto keys often includes the version of the key which needs to be removed + // format: projects//locations//keyRings//cryptoKeys//cryptoKeyVersions/1 + transformed["kmsKeyName"] = strings.Split(kmsKeyName.(string), "/cryptoKeyVersions")[0] + } + + if kmsKeyServiceAccount, ok := original["kmsKeyServiceAccount"]; ok { + transformed["kmsKeyServiceAccount"] = kmsKeyServiceAccount + } + + res["snapshotEncryptionKey"] = transformed + } + + if v, ok := res["sourceDiskEncryptionKey"]; ok { + original := v.(map[string]interface{}) + transformed := make(map[string]interface{}) + // The raw key won't be returned, so we need to use the original. + transformed["rawKey"] = d.Get("source_disk_encryption_key.0.raw_key") + transformed["sha256"] = original["sha256"] + + if kmsKeyName, ok := original["kmsKeyName"]; ok { + // The response for crypto keys often includes the version of the key which needs to be removed + // format: projects//locations//keyRings//cryptoKeys//cryptoKeyVersions/1 + transformed["kmsKeyName"] = strings.Split(kmsKeyName.(string), "/cryptoKeyVersions")[0] + } + + if kmsKeyServiceAccount, ok := original["kmsKeyServiceAccount"]; ok { + transformed["kmsKeyServiceAccount"] = kmsKeyServiceAccount + } + + res["sourceDiskEncryptionKey"] = transformed + } + d.Set("source_disk_link", ConvertSelfLinkToV1(res["sourceDisk"].(string))) return res, nil } diff --git a/google-beta/resource_compute_snapshot_generated_test.go b/google-beta/resource_compute_snapshot_generated_test.go index ea68adef3e..95ff84eddd 100644 --- a/google-beta/resource_compute_snapshot_generated_test.go +++ b/google-beta/resource_compute_snapshot_generated_test.go @@ -57,6 +57,7 @@ resource "google_compute_snapshot" "snapshot" { labels = { my_label = "value" } + storage_locations = ["us-central1"] } data "google_compute_image" "debian" { diff --git a/google-beta/resource_compute_snapshot_test.go b/google-beta/resource_compute_snapshot_test.go index ac5f457f39..5aa7610554 100644 --- a/google-beta/resource_compute_snapshot_test.go +++ b/google-beta/resource_compute_snapshot_test.go @@ -31,6 +31,30 @@ func TestAccComputeSnapshot_encryption(t *testing.T) { }) } +func TestAccComputeSnapshot_encryptionCMEK(t *testing.T) { + t.Parallel() + + snapshotName := fmt.Sprintf("tf-test-%s", randString(t, 10)) + diskName := fmt.Sprintf("tf-test-%s", randString(t, 10)) + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeSnapshotDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComputeSnapshot_encryptionCMEK(snapshotName, diskName), + }, + { + ResourceName: "google_compute_snapshot.foobar", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"zone", "snapshot_encryption_key", "source_disk_encryption_key"}, + }, + }, + }) +} + func testAccComputeSnapshot_encryption(snapshotName string, diskName string) string { return fmt.Sprintf(` data "google_compute_image" "my_image" { @@ -63,3 +87,56 @@ resource "google_compute_snapshot" "foobar" { } `, diskName, snapshotName) } + +func testAccComputeSnapshot_encryptionCMEK(snapshotName string, diskName string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-10" + project = "debian-cloud" +} + +resource "google_service_account" "test" { + account_id = "%s" + display_name = "KMS Ops Account" +} + +resource "google_kms_key_ring" "keyring" { + name = "%s" + location = "us-central1" +} + +resource "google_kms_crypto_key" "example-key" { + name = "%s" + key_ring = google_kms_key_ring.keyring.id + rotation_period = "100000s" +} + +resource "google_kms_crypto_key_iam_member" "example-key" { + crypto_key_id = google_kms_crypto_key.example-key.id + role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" + member = "serviceAccount:${google_service_account.test.email}" +} + +resource "google_compute_disk" "foobar" { + name = "%s" + size = 10 + type = "pd-ssd" + zone = "us-central1-a" + + disk_encryption_key { + kms_key_self_link = google_kms_crypto_key_iam_member.example-key.crypto_key_id + kms_key_service_account = google_service_account.test.email + } +} + +resource "google_compute_snapshot" "foobar" { + name = "%s" + source_disk = google_compute_disk.foobar.name + zone = "us-central1-a" + snapshot_encryption_key { + kms_key_self_link = google_kms_crypto_key_iam_member.example-key.crypto_key_id + kms_key_service_account = google_service_account.test.email + } +} +`, diskName, diskName, diskName, diskName, snapshotName) +} diff --git a/website/docs/r/compute_disk.html.markdown b/website/docs/r/compute_disk.html.markdown index b23641f926..2ef1492d29 100644 --- a/website/docs/r/compute_disk.html.markdown +++ b/website/docs/r/compute_disk.html.markdown @@ -213,7 +213,7 @@ The `source_image_encryption_key` block supports: * `kms_key_service_account` - (Optional) - The service account used for the encryption request for the given KMS key. + The service account used for the encryption request for the given KMS key. If absent, the Compute Engine Service Agent service account is used. The `disk_encryption_key` block supports: @@ -238,7 +238,7 @@ The `disk_encryption_key` block supports: * `kms_key_service_account` - (Optional) - The service account used for the encryption request for the given KMS key. + The service account used for the encryption request for the given KMS key. If absent, the Compute Engine Service Agent service account is used. The `source_snapshot_encryption_key` block supports: diff --git a/website/docs/r/compute_snapshot.html.markdown b/website/docs/r/compute_snapshot.html.markdown index d62ebeb3f4..1d52c92be3 100644 --- a/website/docs/r/compute_snapshot.html.markdown +++ b/website/docs/r/compute_snapshot.html.markdown @@ -61,6 +61,7 @@ resource "google_compute_snapshot" "snapshot" { labels = { my_label = "value" } + storage_locations = ["us-central1"] } data "google_compute_image" "debian" { @@ -104,6 +105,10 @@ The following arguments are supported: (Optional) An optional description of this resource. +* `storage_locations` - + (Optional) + Cloud Storage bucket storage location of the snapshot (regional or multi-regional). + * `labels` - (Optional) Labels to apply to this Snapshot. @@ -132,7 +137,7 @@ The following arguments are supported: The `snapshot_encryption_key` block supports: * `raw_key` - - (Required) + (Optional) Specifies a 256-bit customer-supplied encryption key, encoded in RFC 4648 base64 to either encrypt or decrypt this resource. **Note**: This property is sensitive and will not be displayed in the plan. @@ -141,6 +146,15 @@ The `snapshot_encryption_key` block supports: The RFC 4648 base64 encoded SHA-256 hash of the customer-supplied encryption key that protects this resource. +* `kms_key_self_link` - + (Optional) + The name of the encryption key that is stored in Google Cloud KMS. + +* `kms_key_service_account` - + (Optional) + The service account used for the encryption request for the given KMS key. + If absent, the Compute Engine Service Agent service account is used. + The `source_disk_encryption_key` block supports: * `raw_key` - @@ -149,6 +163,11 @@ The `source_disk_encryption_key` block supports: RFC 4648 base64 to either encrypt or decrypt this resource. **Note**: This property is sensitive and will not be displayed in the plan. +* `kms_key_service_account` - + (Optional) + The service account used for the encryption request for the given KMS key. + If absent, the Compute Engine Service Agent service account is used. + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are exported: