Skip to content
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

feat: delete ISO even if already attached #131

Merged
101 changes: 99 additions & 2 deletions vultr/resource_vultr_iso_private.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package vultr

import (
"context"
"encoding/json"
"fmt"
"log"
"net"
"strings"
"time"

Expand Down Expand Up @@ -72,7 +74,7 @@ func resourceVultrIsoCreate(ctx context.Context, d *schema.ResourceData, meta in
_, err = waitForIsoAvailable(ctx, d, "complete", []string{"pending"}, "status", meta)
if err != nil {
return diag.Errorf(
"error while waiting for ISO %s to be completed: %s", d.Id(), err)
"error while waiting for ISO %s detach to be completed: %s", d.Id(), err)
}

return resourceVultrIsoRead(ctx, d, meta)
Expand Down Expand Up @@ -107,7 +109,71 @@ func resourceVultrIsoDelete(ctx context.Context, d *schema.ResourceData, meta in
log.Printf("[INFO] Deleting iso : %s", d.Id())

if err := client.ISO.Delete(ctx, d.Id()); err != nil {
return diag.Errorf("error destroying ISO %s : %v", d.Id(), err)
// decode the error
var attachedErr struct {
Error string `json:"error"`
Status int `json:"status"`
}

errR := strings.NewReader(err.Error())
jsonErr := json.NewDecoder(errR).Decode(&attachedErr)
if jsonErr != nil {
return diag.Errorf("error destroying ISO %s : %v: %v", d.Id(), err, jsonErr)
}

if !strings.Contains(attachedErr.Error, "is still attached to") {
return diag.Errorf("error destroying ISO %s : %v: %v", d.Id(), err)
}

parts := strings.Split(attachedErr.Error, " ")
ip := parts[len(parts)-1]
if parsedIP := net.ParseIP(ip); parsedIP == nil {
return diag.Errorf("error destroying ISO %s : failed to parse IP to which ISO is attached", d.Id())
}

m := govultr.ListOptions{
PerPage: 50,
}
insts, imeta, err := client.Instance.List(ctx, &m)
if err != nil {
return diag.Errorf("error destroying ISO %s : failed to list instances for detaching ISO", d.Id(), err)
}

// check for the instance with this IP, return on failure or discovery
for _, instance := range insts {
if instance.MainIP == ip {
if err := client.Instance.DetachISO(ctx, instance.ID); err != nil {
return diag.Errorf("error destroying ISO %s : failed to detach from instances %s : %s", d.Id(), instance.ID, err)
}
_, err := waitForIsoDetached(ctx, instance.ID, "ready", []string{"isomounted"}, "status", meta)
if err != nil {
return diag.Errorf("error while waiting for ISO %s to detach from instance %s: %s", d.Id(), instance.ID, err)
}
return resourceVultrIsoDelete(ctx, d, meta) // warning: possible infinite recursion
}
}

// in case it wasn't in the first batch
for imeta.Links.Next != "" {
m.Cursor = imeta.Links.Next
insts, _, err = client.Instance.List(ctx, &m)
if err != nil {
return diag.Errorf("error destroying ISO %s : failed to obtain list of instances using cursor %s", d.Id(), m.Cursor)
}

for _, instance := range insts {
if instance.MainIP == ip {
if err := client.Instance.DetachISO(ctx, instance.ID); err != nil {
return diag.Errorf("error destroying ISO %s : failed to detach from instances %s : %s", d.Id(), instance.ID, err)
}
_, err := waitForIsoDetached(ctx, instance.ID, "ready", []string{"isomounted"}, "status", meta)
if err != nil {
return diag.Errorf("error while waiting for ISO %s to detach from instance %s: %s", d.Id(), instance.ID, err)
}
return resourceVultrIsoDelete(ctx, d, meta) // warning: possible infinite recursion
}
}
}
}

return nil
Expand Down Expand Up @@ -148,3 +214,34 @@ func newIsoStateRefresh(ctx context.Context,
return &iso, iso.Status, nil
}
}

func waitForIsoDetached(ctx context.Context, instanceID string, target string, pending []string, attribute string, meta interface{}) (interface{}, error) {
log.Printf(
"[INFO] Waiting for ISO to detach from %s",
instanceID)

stateConf := &resource.StateChangeConf{
Pending: pending,
Target: []string{target},
Refresh: isoDetachStateRefresh(ctx, instanceID, meta, attribute),
Timeout: 60 * time.Minute,
Delay: 10 * time.Second,
MinTimeout: 3 * time.Second,
NotFoundChecks: 60,
}

return stateConf.WaitForStateContext(ctx)
}

func isoDetachStateRefresh(ctx context.Context, instanceID string, meta interface{}, attr string) resource.StateRefreshFunc {
client := meta.(*Client).govultrClient()
return func() (interface{}, string, error) {

log.Printf("[INFO] Detaching ISO")
iso, err := client.Instance.ISOStatus(ctx, instanceID)
if err != nil {
return nil, "", fmt.Errorf("error getting ISO status for instance %s : %s", instanceID, err)
}
return &iso, iso.State, nil
}
}