volume.go

97 lines
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
package digitalocean

import (
	"fmt"
	"strings"

	"congo.gg/pkg/platform"
	"github.com/digitalocean/godo"
)

// CreateVolume creates a block storage volume
func (b *backend) CreateVolume(name string, sizeGB int, region platform.Region) (*platform.Volume, error) {
	regionSlug, ok := regions[region]
	if !ok {
		return nil, fmt.Errorf("%w: %s", platform.ErrUnsupportedRegion, region)
	}

	createReq := &godo.VolumeCreateRequest{
		Name:          name,
		Region:        regionSlug,
		SizeGigaBytes: int64(sizeGB),
	}

	vol, _, err := b.client.Storage.CreateVolume(b.ctx, createReq)
	if err != nil {
		return nil, fmt.Errorf("create volume: %w", err)
	}

	return &platform.Volume{
		ID:     vol.ID,
		Name:   vol.Name,
		Size:   int(vol.SizeGigaBytes),
		Region: vol.Region.Slug,
	}, nil
}

// GetVolume retrieves a volume by name
func (b *backend) GetVolume(name string) (*platform.Volume, error) {
	volumes, _, err := b.client.Storage.ListVolumes(b.ctx, &godo.ListVolumeParams{Name: name})
	if err != nil {
		return nil, fmt.Errorf("list volumes: %w", err)
	}

	for _, v := range volumes {
		if v.Name == name {
			serverID := ""
			if len(v.DropletIDs) > 0 {
				serverID = fmt.Sprintf("%d", v.DropletIDs[0])
			}
			return &platform.Volume{
				ID:       v.ID,
				Name:     v.Name,
				Size:     int(v.SizeGigaBytes),
				Region:   v.Region.Slug,
				ServerID: serverID,
			}, nil
		}
	}

	return nil, platform.ErrNotFound
}

// AttachVolume attaches a volume to a droplet
func (b *backend) AttachVolume(volumeID, serverID string) error {
	var dropletID int
	if _, err := fmt.Sscanf(serverID, "%d", &dropletID); err != nil {
		return fmt.Errorf("invalid server ID %q: %w", serverID, err)
	}

	_, _, err := b.client.StorageActions.Attach(b.ctx, volumeID, dropletID)
	if err != nil {
		return fmt.Errorf("attach volume: %w", err)
	}
	return nil
}

// DetachVolume detaches a volume from its droplet
func (b *backend) DetachVolume(volumeID string) error {
	// Get volume to find attached droplet
	vol, _, err := b.client.Storage.GetVolume(b.ctx, volumeID)
	if err != nil {
		return fmt.Errorf("get volume: %w", err)
	}

	if len(vol.DropletIDs) == 0 {
		return nil // Already detached
	}

	_, _, err = b.client.StorageActions.DetachByDropletID(b.ctx, volumeID, vol.DropletIDs[0])
	if err != nil {
		if strings.Contains(err.Error(), "not found") {
			return nil // Already detached
		}
		return fmt.Errorf("detach volume: %w", err)
	}
	return nil
}