Merge pull request #14651 from hashicorp/paddy_gcp_detach_deleted_disks

provider/google: detach disks before deleting them.
This commit is contained in:
Paddy 2017-05-23 15:07:19 -07:00 committed by GitHub
commit dd17c1a78c
2 changed files with 147 additions and 0 deletions

View File

@ -10,6 +10,14 @@ import (
"google.golang.org/api/googleapi"
)
const (
computeDiskUserRegexString = "^(?:https://www.googleapis.com/compute/v1/projects/)?([-_a-zA-Z0-9]*)/zones/([-_a-zA-Z0-9]*)/instances/([-_a-zA-Z0-9]*)$"
)
var (
computeDiskUserRegex = regexp.MustCompile(computeDiskUserRegexString)
)
func resourceComputeDisk() *schema.Resource {
return &schema.Resource{
Create: resourceComputeDiskCreate,
@ -75,6 +83,11 @@ func resourceComputeDisk() *schema.Resource {
Optional: true,
ForceNew: true,
},
"users": &schema.Schema{
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
}
@ -186,6 +199,7 @@ func resourceComputeDiskRead(d *schema.ResourceData, meta interface{}) error {
if disk.DiskEncryptionKey != nil && disk.DiskEncryptionKey.Sha256 != "" {
d.Set("disk_encryption_key_sha256", disk.DiskEncryptionKey.Sha256)
}
d.Set("users", disk.Users)
return nil
}
@ -198,6 +212,52 @@ func resourceComputeDiskDelete(d *schema.ResourceData, meta interface{}) error {
return err
}
// if disks are attached, they must be detached before the disk can be deleted
if instances, ok := d.Get("users").([]interface{}); ok {
type detachArgs struct{ project, zone, instance, deviceName string }
var detachCalls []detachArgs
self := d.Get("self_link").(string)
for _, instance := range instances {
if !computeDiskUserRegex.MatchString(instance.(string)) {
return fmt.Errorf("Unknown user %q of disk %q", instance, self)
}
matches := computeDiskUserRegex.FindStringSubmatch(instance.(string))
instanceProject := matches[1]
instanceZone := matches[2]
instanceName := matches[3]
i, err := config.clientCompute.Instances.Get(instanceProject, instanceZone, instanceName).Do()
if err != nil {
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
log.Printf("[WARN] instance %q not found, not bothering to detach disks", instance.(string))
continue
}
return fmt.Errorf("Error retrieving instance %s: %s", instance.(string), err.Error())
}
for _, disk := range i.Disks {
if disk.Source == self {
detachCalls = append(detachCalls, detachArgs{
project: project,
zone: i.Zone,
instance: i.Name,
deviceName: disk.DeviceName,
})
}
}
}
for _, call := range detachCalls {
op, err := config.clientCompute.Instances.DetachDisk(call.project, call.zone, call.instance, call.deviceName).Do()
if err != nil {
return fmt.Errorf("Error detaching disk %s from instance %s/%s/%s: %s", call.deviceName, call.project,
call.zone, call.instance, err.Error())
}
err = computeOperationWaitZone(config, op, call.project, call.zone,
fmt.Sprintf("Detaching disk from %s/%s/%s", call.project, call.zone, call.instance))
if err != nil {
return err
}
}
}
// Delete the disk
op, err := config.clientCompute.Disks.Delete(
project, d.Get("zone").(string), d.Id()).Do()

View File

@ -3,6 +3,7 @@ package google
import (
"fmt"
"os"
"strconv"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
@ -77,6 +78,40 @@ func TestAccComputeDisk_encryption(t *testing.T) {
})
}
func TestAccComputeDisk_deleteDetach(t *testing.T) {
diskName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
instanceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
var disk compute.Disk
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckComputeDiskDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccComputeDisk_deleteDetach(instanceName, diskName),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeDiskExists(
"google_compute_disk.foo", &disk),
),
},
// this needs to be a second step so we refresh and see the instance
// listed as attached to the disk; the instance is created after the
// disk. and the disk's properties aren't refreshed unless there's
// another step
resource.TestStep{
Config: testAccComputeDisk_deleteDetach(instanceName, diskName),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeDiskExists(
"google_compute_disk.foo", &disk),
testAccCheckComputeDiskInstances(
"google_compute_disk.foo", &disk),
),
},
},
})
}
func testAccCheckComputeDiskDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
@ -144,6 +179,28 @@ func testAccCheckEncryptionKey(n string, disk *compute.Disk) resource.TestCheckF
}
}
func testAccCheckComputeDiskInstances(n string, disk *compute.Disk) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
attr := rs.Primary.Attributes["users.#"]
if strconv.Itoa(len(disk.Users)) != attr {
return fmt.Errorf("Disk %s has mismatched users.\nTF State: %+v\nGCP State: %+v", n, rs.Primary.Attributes["users"], disk.Users)
}
for pos, user := range disk.Users {
if rs.Primary.Attributes["users."+strconv.Itoa(pos)] != user {
return fmt.Errorf("Disk %s has mismatched users.\nTF State: %+v.\nGCP State: %+v",
n, rs.Primary.Attributes["users"], disk.Users)
}
}
return nil
}
}
func testAccComputeDisk_basic(diskName string) string {
return fmt.Sprintf(`
resource "google_compute_disk" "foobar" {
@ -191,3 +248,33 @@ resource "google_compute_disk" "foobar" {
disk_encryption_key_raw = "SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0="
}`, diskName)
}
func testAccComputeDisk_deleteDetach(instanceName, diskName string) string {
return fmt.Sprintf(`
resource "google_compute_disk" "foo" {
name = "%s"
image = "debian-8"
size = 50
type = "pd-ssd"
zone = "us-central1-a"
}
resource "google_compute_instance" "bar" {
name = "%s"
machine_type = "n1-standard-1"
zone = "us-central1-a"
disk {
image = "debian-8"
}
disk {
disk = "${google_compute_disk.foo.name}"
auto_delete = false
}
network_interface {
network = "default"
}
}`, diskName, instanceName)
}