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