1package godo 2 3import ( 4 "context" 5 "fmt" 6 "net/http" 7 "time" 8) 9 10const ( 11 storageBasePath = "v2" 12 storageAllocPath = storageBasePath + "/volumes" 13 storageSnapPath = storageBasePath + "/snapshots" 14) 15 16// StorageService is an interface for interfacing with the storage 17// endpoints of the Digital Ocean API. 18// See: https://docs.digitalocean.com/reference/api/api-reference/#tag/Block-Storage 19type StorageService interface { 20 ListVolumes(context.Context, *ListVolumeParams) ([]Volume, *Response, error) 21 GetVolume(context.Context, string) (*Volume, *Response, error) 22 CreateVolume(context.Context, *VolumeCreateRequest) (*Volume, *Response, error) 23 DeleteVolume(context.Context, string) (*Response, error) 24 ListSnapshots(ctx context.Context, volumeID string, opts *ListOptions) ([]Snapshot, *Response, error) 25 GetSnapshot(context.Context, string) (*Snapshot, *Response, error) 26 CreateSnapshot(context.Context, *SnapshotCreateRequest) (*Snapshot, *Response, error) 27 DeleteSnapshot(context.Context, string) (*Response, error) 28} 29 30// StorageServiceOp handles communication with the storage volumes related methods of the 31// DigitalOcean API. 32type StorageServiceOp struct { 33 client *Client 34} 35 36// ListVolumeParams stores the options you can set for a ListVolumeCall 37type ListVolumeParams struct { 38 Region string `json:"region"` 39 Name string `json:"name"` 40 ListOptions *ListOptions `json:"list_options,omitempty"` 41} 42 43var _ StorageService = &StorageServiceOp{} 44 45// Volume represents a Digital Ocean block store volume. 46type Volume struct { 47 ID string `json:"id"` 48 Region *Region `json:"region"` 49 Name string `json:"name"` 50 SizeGigaBytes int64 `json:"size_gigabytes"` 51 Description string `json:"description"` 52 DropletIDs []int `json:"droplet_ids"` 53 CreatedAt time.Time `json:"created_at"` 54 FilesystemType string `json:"filesystem_type"` 55 FilesystemLabel string `json:"filesystem_label"` 56 Tags []string `json:"tags"` 57} 58 59func (f Volume) String() string { 60 return Stringify(f) 61} 62 63// URN returns the volume ID as a valid DO API URN 64func (f Volume) URN() string { 65 return ToURN("Volume", f.ID) 66} 67 68type storageVolumesRoot struct { 69 Volumes []Volume `json:"volumes"` 70 Links *Links `json:"links"` 71 Meta *Meta `json:"meta"` 72} 73 74type storageVolumeRoot struct { 75 Volume *Volume `json:"volume"` 76 Links *Links `json:"links,omitempty"` 77} 78 79// VolumeCreateRequest represents a request to create a block store 80// volume. 81type VolumeCreateRequest struct { 82 Region string `json:"region"` 83 Name string `json:"name"` 84 Description string `json:"description"` 85 SizeGigaBytes int64 `json:"size_gigabytes"` 86 SnapshotID string `json:"snapshot_id"` 87 FilesystemType string `json:"filesystem_type"` 88 FilesystemLabel string `json:"filesystem_label"` 89 Tags []string `json:"tags"` 90} 91 92// ListVolumes lists all storage volumes. 93func (svc *StorageServiceOp) ListVolumes(ctx context.Context, params *ListVolumeParams) ([]Volume, *Response, error) { 94 path := storageAllocPath 95 if params != nil { 96 if params.Region != "" && params.Name != "" { 97 path = fmt.Sprintf("%s?name=%s®ion=%s", path, params.Name, params.Region) 98 } else if params.Region != "" { 99 path = fmt.Sprintf("%s?region=%s", path, params.Region) 100 } else if params.Name != "" { 101 path = fmt.Sprintf("%s?name=%s", path, params.Name) 102 } 103 104 if params.ListOptions != nil { 105 var err error 106 path, err = addOptions(path, params.ListOptions) 107 if err != nil { 108 return nil, nil, err 109 } 110 } 111 } 112 113 req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) 114 if err != nil { 115 return nil, nil, err 116 } 117 118 root := new(storageVolumesRoot) 119 resp, err := svc.client.Do(ctx, req, root) 120 if err != nil { 121 return nil, resp, err 122 } 123 124 if l := root.Links; l != nil { 125 resp.Links = l 126 } 127 if m := root.Meta; m != nil { 128 resp.Meta = m 129 } 130 131 return root.Volumes, resp, nil 132} 133 134// CreateVolume creates a storage volume. The name must be unique. 135func (svc *StorageServiceOp) CreateVolume(ctx context.Context, createRequest *VolumeCreateRequest) (*Volume, *Response, error) { 136 path := storageAllocPath 137 138 req, err := svc.client.NewRequest(ctx, http.MethodPost, path, createRequest) 139 if err != nil { 140 return nil, nil, err 141 } 142 143 root := new(storageVolumeRoot) 144 resp, err := svc.client.Do(ctx, req, root) 145 if err != nil { 146 return nil, resp, err 147 } 148 return root.Volume, resp, nil 149} 150 151// GetVolume retrieves an individual storage volume. 152func (svc *StorageServiceOp) GetVolume(ctx context.Context, id string) (*Volume, *Response, error) { 153 path := fmt.Sprintf("%s/%s", storageAllocPath, id) 154 155 req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) 156 if err != nil { 157 return nil, nil, err 158 } 159 160 root := new(storageVolumeRoot) 161 resp, err := svc.client.Do(ctx, req, root) 162 if err != nil { 163 return nil, resp, err 164 } 165 166 return root.Volume, resp, nil 167} 168 169// DeleteVolume deletes a storage volume. 170func (svc *StorageServiceOp) DeleteVolume(ctx context.Context, id string) (*Response, error) { 171 path := fmt.Sprintf("%s/%s", storageAllocPath, id) 172 173 req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil) 174 if err != nil { 175 return nil, err 176 } 177 return svc.client.Do(ctx, req, nil) 178} 179 180// SnapshotCreateRequest represents a request to create a block store 181// volume. 182type SnapshotCreateRequest struct { 183 VolumeID string `json:"volume_id"` 184 Name string `json:"name"` 185 Description string `json:"description"` 186 Tags []string `json:"tags"` 187} 188 189// ListSnapshots lists all snapshots related to a storage volume. 190func (svc *StorageServiceOp) ListSnapshots(ctx context.Context, volumeID string, opt *ListOptions) ([]Snapshot, *Response, error) { 191 path := fmt.Sprintf("%s/%s/snapshots", storageAllocPath, volumeID) 192 path, err := addOptions(path, opt) 193 if err != nil { 194 return nil, nil, err 195 } 196 197 req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) 198 if err != nil { 199 return nil, nil, err 200 } 201 202 root := new(snapshotsRoot) 203 resp, err := svc.client.Do(ctx, req, root) 204 if err != nil { 205 return nil, resp, err 206 } 207 208 if l := root.Links; l != nil { 209 resp.Links = l 210 } 211 if m := root.Meta; m != nil { 212 resp.Meta = m 213 } 214 215 return root.Snapshots, resp, nil 216} 217 218// CreateSnapshot creates a snapshot of a storage volume. 219func (svc *StorageServiceOp) CreateSnapshot(ctx context.Context, createRequest *SnapshotCreateRequest) (*Snapshot, *Response, error) { 220 path := fmt.Sprintf("%s/%s/snapshots", storageAllocPath, createRequest.VolumeID) 221 222 req, err := svc.client.NewRequest(ctx, http.MethodPost, path, createRequest) 223 if err != nil { 224 return nil, nil, err 225 } 226 227 root := new(snapshotRoot) 228 resp, err := svc.client.Do(ctx, req, root) 229 if err != nil { 230 return nil, resp, err 231 } 232 return root.Snapshot, resp, nil 233} 234 235// GetSnapshot retrieves an individual snapshot. 236func (svc *StorageServiceOp) GetSnapshot(ctx context.Context, id string) (*Snapshot, *Response, error) { 237 path := fmt.Sprintf("%s/%s", storageSnapPath, id) 238 239 req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil) 240 if err != nil { 241 return nil, nil, err 242 } 243 244 root := new(snapshotRoot) 245 resp, err := svc.client.Do(ctx, req, root) 246 if err != nil { 247 return nil, resp, err 248 } 249 250 return root.Snapshot, resp, nil 251} 252 253// DeleteSnapshot deletes a snapshot. 254func (svc *StorageServiceOp) DeleteSnapshot(ctx context.Context, id string) (*Response, error) { 255 path := fmt.Sprintf("%s/%s", storageSnapPath, id) 256 257 req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil) 258 if err != nil { 259 return nil, err 260 } 261 return svc.client.Do(ctx, req, nil) 262} 263