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
87 changes: 85 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,57 @@ 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"`
}

if unmarshalError := json.Unmarshal([]byte(err.Error()), &attachedErr); unmarshalError != nil {
return diag.Errorf("error deleting ISO %s: parsing error %v in deleting ISO %s : %v", d.Id(), err.Error(), unmarshalError)
}

if !strings.Contains(attachedErr.Error, "is still attached to") {
return diag.Errorf("error deleting ISO %s: delete ISO error not related to attachment: delete error %+v", d.Id(), attachedErr)
}

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

for {
// default is 100 instances
var options govultr.ListOptions
instances, meta, err := client.Instance.List(ctx, &options)
if err != nil {
return diag.Errorf("error deleting 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 instances {
if instance.MainIP == ip {
if err := client.Instance.DetachISO(ctx, instance.ID); err != nil {
return diag.Errorf("error deleting 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 deleting ISO %s: failed to wait for ISO to detach from instance %s: %s", d.Id(), instance.ID, err)
}
if err = client.ISO.Delete(ctx, d.Id()); err != nil {
return diag.Errorf("error deleting ISO %s: failed to delete ISO: %s", d.Id(), err)
}
}
options.Cursor = meta.Links.Next
}

// no more instances to check
if options.Cursor == "" {
break
}
}
return diag.Errorf("failed to identify instance associated with IP %s for deleting ISO %s", ip, d.Id())
}

return nil
Expand Down Expand Up @@ -148,3 +200,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
}
}