images.go raw
1 package linodego
2
3 import (
4 "context"
5 "encoding/json"
6 "io"
7 "time"
8
9 "github.com/go-resty/resty/v2"
10 "github.com/linode/linodego/internal/parseabletime"
11 )
12
13 // ImageStatus represents the status of an Image.
14 type ImageStatus string
15
16 // ImageStatus options start with ImageStatus and include all Image statuses
17 const (
18 ImageStatusCreating ImageStatus = "creating"
19 ImageStatusPendingUpload ImageStatus = "pending_upload"
20 ImageStatusAvailable ImageStatus = "available"
21 )
22
23 // ImageRegionStatus represents the status of an Image's replica.
24 type ImageRegionStatus string
25
26 // ImageRegionStatus options start with ImageRegionStatus and
27 // include all Image replica statuses
28 const (
29 ImageRegionStatusAvailable ImageRegionStatus = "available"
30 ImageRegionStatusCreating ImageRegionStatus = "creating"
31 ImageRegionStatusPending ImageRegionStatus = "pending"
32 ImageRegionStatusPendingReplication ImageRegionStatus = "pending replication"
33 ImageRegionStatusPendingDeletion ImageRegionStatus = "pending deletion"
34 ImageRegionStatusReplicating ImageRegionStatus = "replicating"
35 )
36
37 // ImageRegion represents the status of an Image object in a given Region.
38 type ImageRegion struct {
39 Region string `json:"region"`
40 Status ImageRegionStatus `json:"status"`
41 }
42
43 // Image represents a deployable Image object for use with Linode Instances
44 type Image struct {
45 ID string `json:"id"`
46 CreatedBy string `json:"created_by"`
47 Capabilities []string `json:"capabilities"`
48 Label string `json:"label"`
49 Description string `json:"description"`
50 Type string `json:"type"`
51 Vendor string `json:"vendor"`
52 Status ImageStatus `json:"status"`
53 Size int `json:"size"`
54 TotalSize int `json:"total_size"`
55 IsPublic bool `json:"is_public"`
56
57 // NOTE: IsShared may not currently be available to all users and can only be used with v4beta.
58 IsShared bool `json:"is_shared"`
59
60 Deprecated bool `json:"deprecated"`
61 Regions []ImageRegion `json:"regions"`
62 Tags []string `json:"tags"`
63
64 Updated *time.Time `json:"-"`
65 Created *time.Time `json:"-"`
66 Expiry *time.Time `json:"-"`
67 EOL *time.Time `json:"-"`
68
69 // NOTE: ImageSharing may not currently be available to all users and can only be used with v4beta.
70 ImageSharing ImageSharing `json:"image_sharing"`
71 }
72
73 type ImageSharing struct {
74 SharedWith *ImageSharingSharedWith `json:"shared_with"`
75 SharedBy *ImageSharingSharedBy `json:"shared_by"`
76 }
77
78 type ImageSharingSharedWith struct {
79 ShareGroupCount int `json:"sharegroup_count"`
80 ShareGroupListURL string `json:"sharegroup_list_url"`
81 }
82
83 type ImageSharingSharedBy struct {
84 ShareGroupID int `json:"sharegroup_id"`
85 ShareGroupUUID string `json:"sharegroup_uuid"`
86 ShareGroupLabel string `json:"sharegroup_label"`
87 SourceImageID *string `json:"source_image_id"`
88 }
89
90 // ImageShareEntry represents a shared image entry for an ImageShareGroup
91 type ImageShareEntry struct {
92 ID string `json:"id"`
93 CreatedBy *string `json:"created_by"`
94 Capabilities []string `json:"capabilities"`
95 Label string `json:"label"`
96 Description string `json:"description"`
97 Type string `json:"type"`
98 Vendor *string `json:"vendor"`
99 Status ImageStatus `json:"status"`
100 Size int `json:"size"`
101 TotalSize int `json:"total_size"`
102 IsPublic bool `json:"is_public"`
103 IsShared *bool `json:"is_shared"`
104 Deprecated bool `json:"deprecated"`
105 Regions []ImageRegion `json:"regions"`
106 Tags []string `json:"tags"`
107
108 Updated *time.Time `json:"-"`
109 Created *time.Time `json:"-"`
110 Expiry *time.Time `json:"-"`
111 EOL *time.Time `json:"-"`
112
113 ImageSharing ImageSharing `json:"image_sharing"`
114 }
115
116 // ImageCreateOptions fields are those accepted by CreateImage
117 type ImageCreateOptions struct {
118 DiskID int `json:"disk_id"`
119 Label string `json:"label"`
120 Description string `json:"description,omitempty"`
121 CloudInit bool `json:"cloud_init,omitempty"`
122 Tags *[]string `json:"tags,omitempty"`
123 }
124
125 // ImageUpdateOptions fields are those accepted by UpdateImage
126 type ImageUpdateOptions struct {
127 Label string `json:"label,omitempty"`
128 Description *string `json:"description,omitempty"`
129 Tags *[]string `json:"tags,omitempty"`
130 }
131
132 // ImageReplicateOptions represents the options accepted by the
133 // ReplicateImage(...) function.
134 type ImageReplicateOptions struct {
135 Regions []string `json:"regions"`
136 }
137
138 // ImageCreateUploadResponse fields are those returned by CreateImageUpload
139 type ImageCreateUploadResponse struct {
140 Image *Image `json:"image"`
141 UploadTo string `json:"upload_to"`
142 }
143
144 // ImageCreateUploadOptions fields are those accepted by CreateImageUpload
145 type ImageCreateUploadOptions struct {
146 Region string `json:"region"`
147 Label string `json:"label"`
148 Description string `json:"description,omitempty"`
149 CloudInit bool `json:"cloud_init,omitempty"`
150 Tags *[]string `json:"tags,omitempty"`
151 }
152
153 // ImageUploadOptions fields are those accepted by UploadImage
154 type ImageUploadOptions struct {
155 Region string `json:"region"`
156 Label string `json:"label"`
157 Description string `json:"description,omitempty"`
158 CloudInit bool `json:"cloud_init"`
159 Tags *[]string `json:"tags,omitempty"`
160 Image io.Reader
161 }
162
163 // UnmarshalJSON implements the json.Unmarshaler interface
164 func (i *Image) UnmarshalJSON(b []byte) error {
165 type Mask Image
166
167 p := struct {
168 *Mask
169
170 Updated *parseabletime.ParseableTime `json:"updated"`
171 Created *parseabletime.ParseableTime `json:"created"`
172 Expiry *parseabletime.ParseableTime `json:"expiry"`
173 EOL *parseabletime.ParseableTime `json:"eol"`
174 }{
175 Mask: (*Mask)(i),
176 }
177
178 if err := json.Unmarshal(b, &p); err != nil {
179 return err
180 }
181
182 i.Updated = (*time.Time)(p.Updated)
183 i.Created = (*time.Time)(p.Created)
184 i.Expiry = (*time.Time)(p.Expiry)
185 i.EOL = (*time.Time)(p.EOL)
186
187 return nil
188 }
189
190 // UnmarshalJSON implements the json.Unmarshaler interface
191 func (ise *ImageShareEntry) UnmarshalJSON(b []byte) error {
192 type Mask ImageShareEntry
193
194 p := struct {
195 *Mask
196
197 Updated *parseabletime.ParseableTime `json:"updated"`
198 Created *parseabletime.ParseableTime `json:"created"`
199 Expiry *parseabletime.ParseableTime `json:"expiry"`
200 EOL *parseabletime.ParseableTime `json:"eol"`
201 }{
202 Mask: (*Mask)(ise),
203 }
204
205 if err := json.Unmarshal(b, &p); err != nil {
206 return err
207 }
208
209 ise.Updated = (*time.Time)(p.Updated)
210 ise.Created = (*time.Time)(p.Created)
211 ise.Expiry = (*time.Time)(p.Expiry)
212 ise.EOL = (*time.Time)(p.EOL)
213
214 return nil
215 }
216
217 // GetUpdateOptions converts an Image to ImageUpdateOptions for use in UpdateImage
218 func (i Image) GetUpdateOptions() (iu ImageUpdateOptions) {
219 iu.Label = i.Label
220 iu.Description = copyString(&i.Description)
221
222 return iu
223 }
224
225 // ListImages lists Images.
226 func (c *Client) ListImages(ctx context.Context, opts *ListOptions) ([]Image, error) {
227 return getPaginatedResults[Image](
228 ctx,
229 c,
230 "images",
231 opts,
232 )
233 }
234
235 // GetImage gets the Image with the provided ID.
236 func (c *Client) GetImage(ctx context.Context, imageID string) (*Image, error) {
237 return doGETRequest[Image](
238 ctx,
239 c,
240 formatAPIPath("images/%s", imageID),
241 )
242 }
243
244 // CreateImage creates an Image.
245 func (c *Client) CreateImage(ctx context.Context, opts ImageCreateOptions) (*Image, error) {
246 return doPOSTRequest[Image](
247 ctx,
248 c,
249 "images",
250 opts,
251 )
252 }
253
254 // UpdateImage updates the Image with the specified id.
255 func (c *Client) UpdateImage(ctx context.Context, imageID string, opts ImageUpdateOptions) (*Image, error) {
256 return doPUTRequest[Image](
257 ctx,
258 c,
259 formatAPIPath("images/%s", imageID),
260 opts,
261 )
262 }
263
264 // ReplicateImage replicates an image to a given set of regions.
265 func (c *Client) ReplicateImage(ctx context.Context, imageID string, opts ImageReplicateOptions) (*Image, error) {
266 return doPOSTRequest[Image](
267 ctx,
268 c,
269 formatAPIPath("images/%s/regions", imageID),
270 opts,
271 )
272 }
273
274 // DeleteImage deletes the Image with the specified id.
275 func (c *Client) DeleteImage(ctx context.Context, imageID string) error {
276 return doDELETERequest(
277 ctx,
278 c,
279 formatAPIPath("images/%s", imageID),
280 )
281 }
282
283 // CreateImageUpload creates an Image and an upload URL.
284 func (c *Client) CreateImageUpload(ctx context.Context, opts ImageCreateUploadOptions) (*Image, string, error) {
285 result, err := doPOSTRequest[ImageCreateUploadResponse](
286 ctx,
287 c,
288 "images/upload",
289 opts,
290 )
291 if err != nil {
292 return nil, "", err
293 }
294
295 return result.Image, result.UploadTo, nil
296 }
297
298 // UploadImageToURL uploads the given image to the given upload URL.
299 func (c *Client) UploadImageToURL(ctx context.Context, uploadURL string, image io.Reader) error {
300 // Linode-specific headers do not need to be sent to this endpoint
301 req := resty.New().SetDebug(c.resty.Debug).R().
302 SetContext(ctx).
303 SetContentLength(true).
304 SetHeader("Content-Type", "application/octet-stream").
305 SetBody(image)
306
307 _, err := coupleAPIErrors(req.
308 Put(uploadURL))
309
310 return err
311 }
312
313 // UploadImage creates and uploads an image.
314 func (c *Client) UploadImage(ctx context.Context, opts ImageUploadOptions) (*Image, error) {
315 image, uploadURL, err := c.CreateImageUpload(ctx, ImageCreateUploadOptions{
316 Label: opts.Label,
317 Region: opts.Region,
318 Description: opts.Description,
319 CloudInit: opts.CloudInit,
320 Tags: opts.Tags,
321 })
322 if err != nil {
323 return nil, err
324 }
325
326 return image, c.UploadImageToURL(ctx, uploadURL, opts.Image)
327 }
328