package godo import ( "context" "fmt" "net/http" "time" ) const ( storageBasePath = "v2" storageAllocPath = storageBasePath + "/volumes" storageSnapPath = storageBasePath + "/snapshots" ) // StorageService is an interface for interfacing with the storage // endpoints of the Digital Ocean API. // See: https://developers.digitalocean.com/documentation/v2/#block-storage type StorageService interface { ListVolumes(context.Context, *ListVolumeParams) ([]Volume, *Response, error) GetVolume(context.Context, string) (*Volume, *Response, error) CreateVolume(context.Context, *VolumeCreateRequest) (*Volume, *Response, error) DeleteVolume(context.Context, string) (*Response, error) ListSnapshots(ctx context.Context, volumeID string, opts *ListOptions) ([]Snapshot, *Response, error) GetSnapshot(context.Context, string) (*Snapshot, *Response, error) CreateSnapshot(context.Context, *SnapshotCreateRequest) (*Snapshot, *Response, error) DeleteSnapshot(context.Context, string) (*Response, error) } // StorageServiceOp handles communication with the storage volumes related methods of the // DigitalOcean API. type StorageServiceOp struct { client *Client } // ListVolumeParams stores the options you can set for a ListVolumeCall type ListVolumeParams struct { Region string `json:"region"` Name string `json:"name"` ListOptions *ListOptions `json:"list_options,omitempty"` } var _ StorageService = &StorageServiceOp{} // Volume represents a Digital Ocean block store volume. type Volume struct { ID string `json:"id"` Region *Region `json:"region"` Name string `json:"name"` SizeGigaBytes int64 `json:"size_gigabytes"` Description string `json:"description"` DropletIDs []int `json:"droplet_ids"` CreatedAt time.Time `json:"created_at"` FilesystemType string `json:"filesystem_type"` FilesystemLabel string `json:"filesystem_label"` Tags []string `json:"tags"` } func (f Volume) String() string { return Stringify(f) } func (f Volume) URN() string { return ToURN("Volume", f.ID) } type storageVolumesRoot struct { Volumes []Volume `json:"volumes"` Links *Links `json:"links"` Meta *Meta `json:"meta"` } type storageVolumeRoot struct { Volume *Volume `json:"volume"` Links *Links `json:"links,omitempty"` } // VolumeCreateRequest represents a request to create a block store // volume. type VolumeCreateRequest struct { Region string `json:"region"` Name string `json:"name"` Description string `json:"description"` SizeGigaBytes int64 `json:"size_gigabytes"` SnapshotID string `json:"snapshot_id"` FilesystemType string `json:"filesystem_type"` FilesystemLabel string `json:"filesystem_label"` Tags []string `json:"tags"` } // ListVolumes lists all storage volumes. func (svc *StorageServiceOp) ListVolumes(ctx context.Context, params *ListVolumeParams) ([]Volume, *Response, error) { path := storageAllocPath if params != nil { if params.Region != "" && params.Name != "" { path = fmt.Sprintf("%s?name=%s®ion=%s", path, params.Name, params.Region) } else if params.Region != "" { path = fmt.Sprintf("%s?region=%s", path, params.Region) } else if params.Name != "" { path = fmt.Sprintf("%s?name=%s", path, params.Name) } if params.ListOptions != nil { var err error path, err = addOptions(path, params.ListOptions) if err != nil { return nil, nil, err } } } req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) if err != nil { return nil, nil, err } root := new(storageVolumesRoot) resp, err := svc.client.Do(ctx, req, root) if err != nil { return nil, resp, err } if l := root.Links; l != nil { resp.Links = l } if m := root.Meta; m != nil { resp.Meta = m } return root.Volumes, resp, nil } // CreateVolume creates a storage volume. The name must be unique. func (svc *StorageServiceOp) CreateVolume(ctx context.Context, createRequest *VolumeCreateRequest) (*Volume, *Response, error) { path := storageAllocPath req, err := svc.client.NewRequest(ctx, http.MethodPost, path, createRequest) if err != nil { return nil, nil, err } root := new(storageVolumeRoot) resp, err := svc.client.Do(ctx, req, root) if err != nil { return nil, resp, err } return root.Volume, resp, nil } // GetVolume retrieves an individual storage volume. func (svc *StorageServiceOp) GetVolume(ctx context.Context, id string) (*Volume, *Response, error) { path := fmt.Sprintf("%s/%s", storageAllocPath, id) req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) if err != nil { return nil, nil, err } root := new(storageVolumeRoot) resp, err := svc.client.Do(ctx, req, root) if err != nil { return nil, resp, err } return root.Volume, resp, nil } // DeleteVolume deletes a storage volume. func (svc *StorageServiceOp) DeleteVolume(ctx context.Context, id string) (*Response, error) { path := fmt.Sprintf("%s/%s", storageAllocPath, id) req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil) if err != nil { return nil, err } return svc.client.Do(ctx, req, nil) } // SnapshotCreateRequest represents a request to create a block store // volume. type SnapshotCreateRequest struct { VolumeID string `json:"volume_id"` Name string `json:"name"` Description string `json:"description"` Tags []string `json:"tags"` } // ListSnapshots lists all snapshots related to a storage volume. func (svc *StorageServiceOp) ListSnapshots(ctx context.Context, volumeID string, opt *ListOptions) ([]Snapshot, *Response, error) { path := fmt.Sprintf("%s/%s/snapshots", storageAllocPath, volumeID) path, err := addOptions(path, opt) if err != nil { return nil, nil, err } req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) if err != nil { return nil, nil, err } root := new(snapshotsRoot) resp, err := svc.client.Do(ctx, req, root) if err != nil { return nil, resp, err } if l := root.Links; l != nil { resp.Links = l } if m := root.Meta; m != nil { resp.Meta = m } return root.Snapshots, resp, nil } // CreateSnapshot creates a snapshot of a storage volume. func (svc *StorageServiceOp) CreateSnapshot(ctx context.Context, createRequest *SnapshotCreateRequest) (*Snapshot, *Response, error) { path := fmt.Sprintf("%s/%s/snapshots", storageAllocPath, createRequest.VolumeID) req, err := svc.client.NewRequest(ctx, http.MethodPost, path, createRequest) if err != nil { return nil, nil, err } root := new(snapshotRoot) resp, err := svc.client.Do(ctx, req, root) if err != nil { return nil, resp, err } return root.Snapshot, resp, nil } // GetSnapshot retrieves an individual snapshot. func (svc *StorageServiceOp) GetSnapshot(ctx context.Context, id string) (*Snapshot, *Response, error) { path := fmt.Sprintf("%s/%s", storageSnapPath, id) req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) if err != nil { return nil, nil, err } root := new(snapshotRoot) resp, err := svc.client.Do(ctx, req, root) if err != nil { return nil, resp, err } return root.Snapshot, resp, nil } // DeleteSnapshot deletes a snapshot. func (svc *StorageServiceOp) DeleteSnapshot(ctx context.Context, id string) (*Response, error) { path := fmt.Sprintf("%s/%s", storageSnapPath, id) req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil) if err != nil { return nil, err } return svc.client.Do(ctx, req, nil) }