1package linodego 2 3import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "time" 8 9 "github.com/linode/linodego/internal/parseabletime" 10) 11 12// VolumeStatus indicates the status of the Volume 13type VolumeStatus string 14 15const ( 16 // VolumeCreating indicates the Volume is being created and is not yet available for use 17 VolumeCreating VolumeStatus = "creating" 18 19 // VolumeActive indicates the Volume is online and available for use 20 VolumeActive VolumeStatus = "active" 21 22 // VolumeResizing indicates the Volume is in the process of upgrading its current capacity 23 VolumeResizing VolumeStatus = "resizing" 24 25 // VolumeContactSupport indicates there is a problem with the Volume. A support ticket must be opened to resolve the issue 26 VolumeContactSupport VolumeStatus = "contact_support" 27) 28 29// Volume represents a linode volume object 30type Volume struct { 31 ID int `json:"id"` 32 Label string `json:"label"` 33 Status VolumeStatus `json:"status"` 34 Region string `json:"region"` 35 Size int `json:"size"` 36 LinodeID *int `json:"linode_id"` 37 FilesystemPath string `json:"filesystem_path"` 38 Tags []string `json:"tags"` 39 Created *time.Time `json:"-"` 40 Updated *time.Time `json:"-"` 41} 42 43// VolumeCreateOptions fields are those accepted by CreateVolume 44type VolumeCreateOptions struct { 45 Label string `json:"label,omitempty"` 46 Region string `json:"region,omitempty"` 47 LinodeID int `json:"linode_id,omitempty"` 48 ConfigID int `json:"config_id,omitempty"` 49 // The Volume's size, in GiB. Minimum size is 10GiB, maximum size is 10240GiB. A "0" value will result in the default size. 50 Size int `json:"size,omitempty"` 51 // An array of tags applied to this object. Tags are for organizational purposes only. 52 Tags []string `json:"tags"` 53 PersistAcrossBoots *bool `json:"persist_across_boots,omitempty"` 54} 55 56// VolumeUpdateOptions fields are those accepted by UpdateVolume 57type VolumeUpdateOptions struct { 58 Label string `json:"label,omitempty"` 59 Tags *[]string `json:"tags,omitempty"` 60} 61 62// VolumeAttachOptions fields are those accepted by AttachVolume 63type VolumeAttachOptions struct { 64 LinodeID int `json:"linode_id"` 65 ConfigID int `json:"config_id,omitempty"` 66 PersistAcrossBoots *bool `json:"persist_across_boots,omitempty"` 67} 68 69// VolumesPagedResponse represents a linode API response for listing of volumes 70type VolumesPagedResponse struct { 71 *PageOptions 72 Data []Volume `json:"data"` 73} 74 75// UnmarshalJSON implements the json.Unmarshaler interface 76func (v *Volume) UnmarshalJSON(b []byte) error { 77 type Mask Volume 78 79 p := struct { 80 *Mask 81 Created *parseabletime.ParseableTime `json:"created"` 82 Updated *parseabletime.ParseableTime `json:"updated"` 83 }{ 84 Mask: (*Mask)(v), 85 } 86 87 if err := json.Unmarshal(b, &p); err != nil { 88 return err 89 } 90 91 v.Created = (*time.Time)(p.Created) 92 v.Updated = (*time.Time)(p.Updated) 93 94 return nil 95} 96 97// GetUpdateOptions converts a Volume to VolumeUpdateOptions for use in UpdateVolume 98func (v Volume) GetUpdateOptions() (updateOpts VolumeUpdateOptions) { 99 updateOpts.Label = v.Label 100 updateOpts.Tags = &v.Tags 101 return 102} 103 104// GetCreateOptions converts a Volume to VolumeCreateOptions for use in CreateVolume 105func (v Volume) GetCreateOptions() (createOpts VolumeCreateOptions) { 106 createOpts.Label = v.Label 107 createOpts.Tags = v.Tags 108 createOpts.Region = v.Region 109 createOpts.Size = v.Size 110 if v.LinodeID != nil && *v.LinodeID > 0 { 111 createOpts.LinodeID = *v.LinodeID 112 } 113 return 114} 115 116// endpoint gets the endpoint URL for Volume 117func (VolumesPagedResponse) endpoint(c *Client) string { 118 endpoint, err := c.Volumes.Endpoint() 119 if err != nil { 120 panic(err) 121 } 122 return endpoint 123} 124 125// appendData appends Volumes when processing paginated Volume responses 126func (resp *VolumesPagedResponse) appendData(r *VolumesPagedResponse) { 127 resp.Data = append(resp.Data, r.Data...) 128} 129 130// ListVolumes lists Volumes 131func (c *Client) ListVolumes(ctx context.Context, opts *ListOptions) ([]Volume, error) { 132 response := VolumesPagedResponse{} 133 err := c.listHelper(ctx, &response, opts) 134 if err != nil { 135 return nil, err 136 } 137 return response.Data, nil 138} 139 140// GetVolume gets the template with the provided ID 141func (c *Client) GetVolume(ctx context.Context, id int) (*Volume, error) { 142 e, err := c.Volumes.Endpoint() 143 if err != nil { 144 return nil, err 145 } 146 e = fmt.Sprintf("%s/%d", e, id) 147 r, err := coupleAPIErrors(c.R(ctx).SetResult(&Volume{}).Get(e)) 148 if err != nil { 149 return nil, err 150 } 151 return r.Result().(*Volume), nil 152} 153 154// AttachVolume attaches a volume to a Linode instance 155func (c *Client) AttachVolume(ctx context.Context, id int, options *VolumeAttachOptions) (*Volume, error) { 156 body := "" 157 if bodyData, err := json.Marshal(options); err == nil { 158 body = string(bodyData) 159 } else { 160 return nil, NewError(err) 161 } 162 163 e, err := c.Volumes.Endpoint() 164 if err != nil { 165 return nil, NewError(err) 166 } 167 168 e = fmt.Sprintf("%s/%d/attach", e, id) 169 resp, err := coupleAPIErrors(c.R(ctx). 170 SetResult(&Volume{}). 171 SetBody(body). 172 Post(e)) 173 if err != nil { 174 return nil, err 175 } 176 177 return resp.Result().(*Volume), nil 178} 179 180// CreateVolume creates a Linode Volume 181func (c *Client) CreateVolume(ctx context.Context, createOpts VolumeCreateOptions) (*Volume, error) { 182 body := "" 183 if bodyData, err := json.Marshal(createOpts); err == nil { 184 body = string(bodyData) 185 } else { 186 return nil, NewError(err) 187 } 188 189 e, err := c.Volumes.Endpoint() 190 if err != nil { 191 return nil, NewError(err) 192 } 193 194 resp, err := coupleAPIErrors(c.R(ctx). 195 SetResult(&Volume{}). 196 SetBody(body). 197 Post(e)) 198 if err != nil { 199 return nil, err 200 } 201 202 return resp.Result().(*Volume), nil 203} 204 205// UpdateVolume updates the Volume with the specified id 206func (c *Client) UpdateVolume(ctx context.Context, id int, volume VolumeUpdateOptions) (*Volume, error) { 207 var body string 208 e, err := c.Volumes.Endpoint() 209 if err != nil { 210 return nil, err 211 } 212 e = fmt.Sprintf("%s/%d", e, id) 213 214 req := c.R(ctx).SetResult(&Volume{}) 215 216 if bodyData, err := json.Marshal(volume); err == nil { 217 body = string(bodyData) 218 } else { 219 return nil, NewError(err) 220 } 221 222 r, err := coupleAPIErrors(req. 223 SetBody(body). 224 Put(e)) 225 if err != nil { 226 return nil, err 227 } 228 return r.Result().(*Volume), nil 229} 230 231// CloneVolume clones a Linode volume 232func (c *Client) CloneVolume(ctx context.Context, id int, label string) (*Volume, error) { 233 body := fmt.Sprintf("{\"label\":\"%s\"}", label) 234 235 e, err := c.Volumes.Endpoint() 236 if err != nil { 237 return nil, NewError(err) 238 } 239 e = fmt.Sprintf("%s/%d/clone", e, id) 240 241 resp, err := coupleAPIErrors(c.R(ctx). 242 SetResult(&Volume{}). 243 SetBody(body). 244 Post(e)) 245 if err != nil { 246 return nil, err 247 } 248 249 return resp.Result().(*Volume), nil 250} 251 252// DetachVolume detaches a Linode volume 253func (c *Client) DetachVolume(ctx context.Context, id int) error { 254 body := "" 255 256 e, err := c.Volumes.Endpoint() 257 if err != nil { 258 return NewError(err) 259 } 260 261 e = fmt.Sprintf("%s/%d/detach", e, id) 262 263 _, err = coupleAPIErrors(c.R(ctx). 264 SetBody(body). 265 Post(e)) 266 267 return err 268} 269 270// ResizeVolume resizes an instance to new Linode type 271func (c *Client) ResizeVolume(ctx context.Context, id int, size int) error { 272 body := fmt.Sprintf("{\"size\": %d}", size) 273 274 e, err := c.Volumes.Endpoint() 275 if err != nil { 276 return NewError(err) 277 } 278 e = fmt.Sprintf("%s/%d/resize", e, id) 279 280 _, err = coupleAPIErrors(c.R(ctx). 281 SetBody(body). 282 Post(e)) 283 284 return err 285} 286 287// DeleteVolume deletes the Volume with the specified id 288func (c *Client) DeleteVolume(ctx context.Context, id int) error { 289 e, err := c.Volumes.Endpoint() 290 if err != nil { 291 return err 292 } 293 e = fmt.Sprintf("%s/%d", e, id) 294 295 _, err = coupleAPIErrors(c.R(ctx).Delete(e)) 296 return err 297} 298