From 4b4c9de5c1148cc29d6017b529c57840b6e7ec2d Mon Sep 17 00:00:00 2001 From: Sam Levenick Date: Sat, 18 Jan 2020 01:23:50 +0000 Subject: [PATCH] Instance encrypted disk start Signed-off-by: Modular Magician --- google-beta/compute_instance_helpers.go | 29 ++++ google-beta/resource_compute_instance.go | 28 +++- google-beta/resource_compute_instance_test.go | 149 ++++++++++++++++++ 3 files changed, 204 insertions(+), 2 deletions(-) diff --git a/google-beta/compute_instance_helpers.go b/google-beta/compute_instance_helpers.go index 2a0c7481a2..ba9ab707e5 100644 --- a/google-beta/compute_instance_helpers.go +++ b/google-beta/compute_instance_helpers.go @@ -2,6 +2,7 @@ package google import ( "fmt" + "reflect" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" @@ -347,3 +348,31 @@ func flattenEnableDisplay(displayDevice *computeBeta.DisplayDevice) interface{} return displayDevice.EnableDisplay } + +// Terraform doesn't correctly calculate changes on schema.Set, so we do it manually +// https://github.com/hashicorp/terraform-plugin-sdk/issues/98 +func schedulingHasChange(d *schema.ResourceData) bool { + if !d.HasChange("scheduling") { + // This doesn't work correctly, which is why this method exists + // But it is here for posterity + return false + } + o, n := d.GetChange("scheduling") + oScheduling := o.([]interface{})[0].(map[string]interface{}) + newScheduling := n.([]interface{})[0].(map[string]interface{}) + originalNa := oScheduling["node_affinities"].(*schema.Set) + newNa := newScheduling["node_affinities"].(*schema.Set) + if oScheduling["automatic_restart"] != newScheduling["automatic_restart"] { + return true + } + + if oScheduling["preemptible"] != newScheduling["preemptible"] { + return true + } + + if oScheduling["on_host_maintenance"] != newScheduling["on_host_maintenance"] { + return true + } + + return reflect.DeepEqual(newNa, originalNa) +} diff --git a/google-beta/resource_compute_instance.go b/google-beta/resource_compute_instance.go index 76dba4a59f..08247b292b 100644 --- a/google-beta/resource_compute_instance.go +++ b/google-beta/resource_compute_instance.go @@ -422,6 +422,10 @@ func resourceComputeInstance() *schema.Resource { Optional: true, Computed: true, Elem: &schema.Resource{ + // !!! IMPORTANT !!! + // We have a custom diff function for the scheduling block due to issues with Terraform's + // diff on schema.Set. If changes are made to this block, they must be reflected in that + // method. See schedulingHasChange in compute_instance_helpers.go Schema: map[string]*schema.Schema{ "on_host_maintenance": { Type: schema.TypeString, @@ -1051,7 +1055,7 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err d.SetPartial("labels") } - if d.HasChange("scheduling") { + if schedulingHasChange(d) { scheduling, err := expandScheduling(d.Get("scheduling")) if err != nil { return fmt.Errorf("Error creating request data to update scheduling: %s", err) @@ -1399,7 +1403,27 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err d.SetPartial("enable_display") } - op, err = config.clientCompute.Instances.Start(project, zone, instance.Name).Do() + // Retrieve instance from config to pull encryption keys if necessary + instanceFromConfig, err := expandComputeInstance(project, d, config) + if err != nil { + return err + } + + var encrypted []*compute.CustomerEncryptionKeyProtectedDisk + for _, disk := range instanceFromConfig.Disks { + if disk.DiskEncryptionKey != nil { + key := compute.CustomerEncryptionKey{RawKey: disk.DiskEncryptionKey.RawKey, KmsKeyName: disk.DiskEncryptionKey.KmsKeyName} + eDisk := compute.CustomerEncryptionKeyProtectedDisk{Source: disk.Source, DiskEncryptionKey: &key} + encrypted = append(encrypted, &eDisk) + } + } + + if len(encrypted) > 0 { + request := compute.InstancesStartWithEncryptionKeyRequest{Disks: encrypted} + op, err = config.clientCompute.Instances.StartWithEncryptionKey(project, zone, instance.Name, &request).Do() + } else { + op, err = config.clientCompute.Instances.Start(project, zone, instance.Name).Do() + } if err != nil { return errwrap.Wrapf("Error starting instance: {{err}}", err) } diff --git a/google-beta/resource_compute_instance_test.go b/google-beta/resource_compute_instance_test.go index 28f445f3c0..84556d13c5 100644 --- a/google-beta/resource_compute_instance_test.go +++ b/google-beta/resource_compute_instance_test.go @@ -283,6 +283,45 @@ func TestAccComputeInstance_diskEncryption(t *testing.T) { }) } +func TestAccComputeInstance_diskEncryptionRestart(t *testing.T) { + t.Parallel() + + var instance compute.Instance + var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10)) + bootEncryptionKey := "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=" + bootEncryptionKeyHash := "esTuF7d4eatX4cnc4JsiEiaI+Rff78JgPhA/v1zxX9E=" + diskNameToEncryptionKey := map[string]*compute.CustomerEncryptionKey{ + fmt.Sprintf("instance-testd-%s", acctest.RandString(10)): { + RawKey: "Ym9vdDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=", + Sha256: "awJ7p57H+uVZ9axhJjl1D3lfC2MgA/wnt/z88Ltfvss=", + }, + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccComputeInstance_disks_encryption_restart(bootEncryptionKey, diskNameToEncryptionKey, instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceDiskEncryptionKey("google_compute_instance.foobar", &instance, bootEncryptionKeyHash, diskNameToEncryptionKey), + ), + }, + { + Config: testAccComputeInstance_disks_encryption_restartUpdate(bootEncryptionKey, diskNameToEncryptionKey, instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceDiskEncryptionKey("google_compute_instance.foobar", &instance, bootEncryptionKeyHash, diskNameToEncryptionKey), + ), + }, + }, + }) +} + func TestAccComputeInstance_kmsDiskEncryption(t *testing.T) { t.Parallel() @@ -2289,6 +2328,8 @@ resource "google_compute_instance" "foobar" { metadata = { foo = "bar" } + + allow_stopping_for_update = true } `, diskNames[0], diskNameToEncryptionKey[diskNames[0]].RawKey, diskNames[1], diskNameToEncryptionKey[diskNames[1]].RawKey, @@ -2298,6 +2339,114 @@ resource "google_compute_instance" "foobar" { diskNameToEncryptionKey[diskNames[0]].RawKey, diskNameToEncryptionKey[diskNames[1]].RawKey, diskNameToEncryptionKey[diskNames[2]].RawKey) } +func testAccComputeInstance_disks_encryption_restart(bootEncryptionKey string, diskNameToEncryptionKey map[string]*compute.CustomerEncryptionKey, instance string) string { + diskNames := []string{} + for k := range diskNameToEncryptionKey { + diskNames = append(diskNames, k) + } + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_disk" "foobar" { + name = "%s" + size = 10 + type = "pd-ssd" + zone = "us-central1-a" + + disk_encryption_key { + raw_key = "%s" + } +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params { + image = data.google_compute_image.my_image.self_link + } + disk_encryption_key_raw = "%s" + } + + attached_disk { + source = google_compute_disk.foobar.self_link + disk_encryption_key_raw = "%s" + } + + network_interface { + network = "default" + } + + metadata = { + foo = "bar" + } + + allow_stopping_for_update = true +} +`, diskNames[0], diskNameToEncryptionKey[diskNames[0]].RawKey, + instance, bootEncryptionKey, + diskNameToEncryptionKey[diskNames[0]].RawKey) +} + +func testAccComputeInstance_disks_encryption_restartUpdate(bootEncryptionKey string, diskNameToEncryptionKey map[string]*compute.CustomerEncryptionKey, instance string) string { + diskNames := []string{} + for k := range diskNameToEncryptionKey { + diskNames = append(diskNames, k) + } + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_disk" "foobar" { + name = "%s" + size = 10 + type = "pd-ssd" + zone = "us-central1-a" + + disk_encryption_key { + raw_key = "%s" + } +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-2" + zone = "us-central1-a" + + boot_disk { + initialize_params { + image = data.google_compute_image.my_image.self_link + } + disk_encryption_key_raw = "%s" + } + + attached_disk { + source = google_compute_disk.foobar.self_link + disk_encryption_key_raw = "%s" + } + + network_interface { + network = "default" + } + + metadata = { + foo = "bar" + } + + allow_stopping_for_update = true +} +`, diskNames[0], diskNameToEncryptionKey[diskNames[0]].RawKey, + instance, bootEncryptionKey, + diskNameToEncryptionKey[diskNames[0]].RawKey) +} + func testAccComputeInstance_disks_kms(pid string, bootEncryptionKey string, diskNameToEncryptionKey map[string]*compute.CustomerEncryptionKey, instance string) string { diskNames := []string{} for k := range diskNameToEncryptionKey {