Skip to content

Add a functionality to create a snapshot before a disk is deleted #9442

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
6 changes: 6 additions & 0 deletions .changelog/12947.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
```release-note:enhancement
compute: added `create_snapshot_before_destroy` to `google_compute_disk` and `google_compute_region_disk` to enable creating a snapshot before disk deletion
```
```release-note:enhancement
compute: added `rsa_encrypted_key` to `google_compute_region_disk`
```
72 changes: 72 additions & 0 deletions google-beta/services/compute/resource_compute_disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
compute "google.golang.org/api/compute/v0.beta"
"google.golang.org/api/googleapi"

"github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource"
Expand Down Expand Up @@ -883,6 +884,19 @@ project/zones/zone/instances/instance`,
Type: schema.TypeString,
},
},
"create_snapshot_before_destroy": {
Type: schema.TypeBool,
Optional: true,
Description: `If set to true, a snapshot of the disk will be created before it is destroyed.
If your disk is encrypted with customer managed encryption keys these will be reused for the snapshot creation.
The name of the snapshot by default will be '{{disk-name}}-YYYYMMDD-HHmm'`,
Default: false,
},
"create_snapshot_before_destroy_prefix": {
Type: schema.TypeString,
Optional: true,
Description: `This will set a custom name prefix for the snapshot that's created when the disk is deleted.`,
},
"project": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -1199,6 +1213,12 @@ func resourceComputeDiskRead(d *schema.ResourceData, meta interface{}) error {
return nil
}

// Explicitly set virtual fields to default values if unset
if _, ok := d.GetOkExists("create_snapshot_before_destroy"); !ok {
if err := d.Set("create_snapshot_before_destroy", false); err != nil {
return fmt.Errorf("Error setting create_snapshot_before_destroy: %s", err)
}
}
if err := d.Set("project", project); err != nil {
return fmt.Errorf("Error reading Disk: %s", err)
}
Expand Down Expand Up @@ -1636,6 +1656,53 @@ func resourceComputeDiskDelete(d *schema.ResourceData, meta interface{}) error {
return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("ComputeDisk %q", d.Id()))
}

// if the create_snapshot_before_destroy is set to true then create a snapshot before deleting the disk
if d.Get("create_snapshot_before_destroy").(bool) {
instanceName := d.Get("name").(string)
nameOrigin := "disk"
if d.Get("create_snapshot_before_destroy_prefix").(string) != "" {
instanceName = d.Get("create_snapshot_before_destroy_prefix").(string)
nameOrigin = "create_snapshot_before_destroy_prefix"
}

if len(instanceName) > 48 {
return fmt.Errorf(`Your %s name is too long to perform this action. The max is 48 characters. Please use "create_snapshot_before_destroy_prefix" to set a custom name for the snapshot.`, nameOrigin)
}

snapshotObj := &compute.Snapshot{
Name: fmt.Sprintf("%s-%s", instanceName, time.Now().Format("20060102-150405")),
SourceDisk: d.Get("self_link").(string),
}

//Handling encryption
if d.Get("disk_encryption_key.0.raw_key").(string) != "" {
snapshotObj.SourceDiskEncryptionKey = &compute.CustomerEncryptionKey{
RawKey: d.Get("disk_encryption_key.0.raw_key").(string),
}
snapshotObj.SnapshotEncryptionKey = &compute.CustomerEncryptionKey{
RawKey: d.Get("disk_encryption_key.0.raw_key").(string),
}
}

if d.Get("disk_encryption_key.0.rsa_encrypted_key").(string) != "" {
snapshotObj.SourceDiskEncryptionKey = &compute.CustomerEncryptionKey{
RsaEncryptedKey: d.Get("disk_encryption_key.0.rsa_encrypted_key").(string),
}
snapshotObj.SnapshotEncryptionKey = &compute.CustomerEncryptionKey{
RsaEncryptedKey: d.Get("disk_encryption_key.0.rsa_encrypted_key").(string),
}
}

snapshot, err := config.NewComputeClient(userAgent).Snapshots.Insert(project, snapshotObj).Do()
if err != nil {
return fmt.Errorf("Error creating snapshot: %s", err)
}
err = ComputeOperationWaitTime(config, snapshot, project, "Creating Snapshot", userAgent, d.Timeout(schema.TimeoutCreate))
if err != nil {
return err
}
}

// if disks are attached to instances, they must be detached before the disk can be deleted
if v, ok := readRes["users"].([]interface{}); ok {
type detachArgs struct{ project, zone, instance, deviceName string }
Expand Down Expand Up @@ -1732,6 +1799,11 @@ func resourceComputeDiskImport(d *schema.ResourceData, meta interface{}) ([]*sch
}
d.SetId(id)

// Explicitly set virtual fields to default values on import
if err := d.Set("create_snapshot_before_destroy", false); err != nil {
return nil, fmt.Errorf("Error setting create_snapshot_before_destroy: %s", err)
}

return []*schema.ResourceData{d}, nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ fields:
- field: 'access_mode'
- field: 'architecture'
- field: 'async_primary_disk.disk'
- field: 'create_snapshot_before_destroy'
provider_only: true
- field: 'create_snapshot_before_destroy_prefix'
provider_only: true
- field: 'creation_timestamp'
- field: 'description'
- field: 'disk_encryption_key.kms_key_self_link'
Expand Down
110 changes: 110 additions & 0 deletions google-beta/services/compute/resource_compute_disk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"os"
"testing"
"time"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
Expand Down Expand Up @@ -888,6 +889,26 @@ func testAccCheckEncryptionKey(t *testing.T, n string, disk *compute.Disk) resou
}
}

func testAccCheckComputeDisk_removeBackupSnapshot(t *testing.T, parentDiskName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
config := acctest.GoogleProviderConfig(t)
snapshot, err := config.NewComputeClient(config.UserAgent).Snapshots.List(envvar.GetTestProjectFromEnv()).Filter(fmt.Sprintf("name eq %s.*", parentDiskName)).Do()
if err != nil {
return err
}

if len(snapshot.Items) == 0 {
return fmt.Errorf("No snapshot found")
}

op, err := config.NewComputeClient(config.UserAgent).Snapshots.Delete(envvar.GetTestProjectFromEnv(), snapshot.Items[0].Name).Do()
if err != nil {
return err
}
return tpgcompute.ComputeOperationWaitTime(config, op, envvar.GetTestProjectFromEnv(), "Deleting Snapshot", config.UserAgent, 10*time.Minute)
}
}

func TestAccComputeDisk_cloneDisk(t *testing.T) {
t.Parallel()
pid := envvar.GetTestProjectFromEnv()
Expand Down Expand Up @@ -1061,6 +1082,52 @@ func TestAccComputeDisk_featuresUpdated(t *testing.T) {
})
}

func TestAccComputeDisk_createSnapshotBeforeDestroy(t *testing.T) {
acctest.SkipIfVcr(t) // Disk cleanup test check
t.Parallel()

var disk1 compute.Disk
var disk2 compute.Disk
var disk3 compute.Disk
context := map[string]interface{}{
"disk_name1": fmt.Sprintf("tf-test-disk-%s", acctest.RandString(t, 10)),
"disk_name2": fmt.Sprintf("test-%s", acctest.RandString(t, 44)), //this is over the snapshot character creation limit of 48
"disk_name3": fmt.Sprintf("tf-test-disk-%s", acctest.RandString(t, 10)),
"snapshot_prefix": fmt.Sprintf("tf-test-snapshot-%s", acctest.RandString(t, 10)),
"kms_key_self_link": acctest.BootstrapKMSKey(t).CryptoKey.Name,
"raw_key": "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=",
"rsa_encrypted_key": "ieCx/NcW06PcT7Ep1X6LUTc/hLvUDYyzSZPPVCVPTVEohpeHASqC8uw5TzyO9U+Fka9JFHz0mBibXUInrC/jEk014kCK/NPjYgEMOyssZ4ZINPKxlUh2zn1bV+MCaTICrdmuSBTWlUUiFoDD6PYznLwh8ZNdaheCeZ8ewEXgFQ8V+sDroLaN3Xs3MDTXQEMMoNUXMCZEIpg9Vtp9x2oeQ5lAbtt7bYAAHf5l+gJWw3sUfs0/Glw5fpdjT8Uggrr+RMZezGrltJEF293rvTIjWOEB3z5OHyHwQkvdrPDFcTqsLfh+8Hr8g+mf+7zVPEC8nEbqpdl3GPv3A7AwpFp7MA==",
}

acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
CheckDestroy: testAccCheckComputeDiskDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccComputeDisk_createSnapshotBeforeDestroy_init(context),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeDiskExists(
t, "google_compute_disk.raw-encrypted-name", envvar.GetTestProjectFromEnv(), &disk1),
testAccCheckComputeDiskExists(
t, "google_compute_disk.rsa-encrypted-prefix", envvar.GetTestProjectFromEnv(), &disk2),
testAccCheckComputeDiskExists(
t, "google_compute_disk.kms-encrypted-name", envvar.GetTestProjectFromEnv(), &disk3),
),
},
{
Config: testAccComputeDisk_createSnapshotBeforeDestroy_init(context),
Destroy: true,
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeDisk_removeBackupSnapshot(t, context["disk_name1"].(string)),
testAccCheckComputeDisk_removeBackupSnapshot(t, context["snapshot_prefix"].(string)),
testAccCheckComputeDisk_removeBackupSnapshot(t, context["disk_name3"].(string)),
),
},
},
})
}

func testAccComputeDisk_basic(diskName string, diskType string) string {
return fmt.Sprintf(`
data "google_compute_image" "my_image" {
Expand Down Expand Up @@ -1999,6 +2066,49 @@ resource "google_compute_disk" "foobar" {
`, diskName, accessMode)
}

func testAccComputeDisk_createSnapshotBeforeDestroy_init(context map[string]interface{}) string {
return acctest.Nprintf(`
resource "google_compute_disk" "raw-encrypted-name" {
name = "%{disk_name1}"
type = "pd-ssd"
size = 10
zone = "us-central1-a"

disk_encryption_key {
raw_key = "%{raw_key}"
}

create_snapshot_before_destroy = true
}

resource "google_compute_disk" "rsa-encrypted-prefix" {
name = "%{disk_name2}"
type = "pd-ssd"
size = 10
zone = "us-central1-a"

disk_encryption_key {
rsa_encrypted_key = "%{rsa_encrypted_key}"
}

create_snapshot_before_destroy = true
create_snapshot_before_destroy_prefix = "%{snapshot_prefix}"
}

resource "google_compute_disk" "kms-encrypted-name" {
name = "%{disk_name3}"
type = "pd-ssd"
size = 10
zone = "us-central1-a"

disk_encryption_key {
kms_key_self_link = "%{kms_key_self_link}"
}

create_snapshot_before_destroy = true
}`, context)
}

func testAccComputeDisk_architecture(context map[string]interface{}) string {
return acctest.Nprintf(`
resource "google_compute_disk" "foobar" {
Expand Down
Loading