1package hcloud 2 3import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "net/url" 9 "strconv" 10 "time" 11 12 "github.com/hetznercloud/hcloud-go/hcloud/schema" 13) 14 15// Image represents an Image in the Hetzner Cloud. 16type Image struct { 17 ID int 18 Name string 19 Type ImageType 20 Status ImageStatus 21 Description string 22 ImageSize float32 23 DiskSize float32 24 Created time.Time 25 CreatedFrom *Server 26 BoundTo *Server 27 RapidDeploy bool 28 29 OSFlavor string 30 OSVersion string 31 32 Protection ImageProtection 33 Deprecated time.Time // The zero value denotes the image is not deprecated. 34 Labels map[string]string 35} 36 37// IsDeprecated returns whether the image is deprecated. 38func (image *Image) IsDeprecated() bool { 39 return !image.Deprecated.IsZero() 40} 41 42// ImageProtection represents the protection level of an image. 43type ImageProtection struct { 44 Delete bool 45} 46 47// ImageType specifies the type of an image. 48type ImageType string 49 50const ( 51 // ImageTypeSnapshot represents a snapshot image. 52 ImageTypeSnapshot ImageType = "snapshot" 53 // ImageTypeBackup represents a backup image. 54 ImageTypeBackup ImageType = "backup" 55 // ImageTypeSystem represents a system image. 56 ImageTypeSystem ImageType = "system" 57) 58 59// ImageStatus specifies the status of an image. 60type ImageStatus string 61 62const ( 63 // ImageStatusCreating is the status when an image is being created. 64 ImageStatusCreating ImageStatus = "creating" 65 // ImageStatusAvailable is the status when an image is available. 66 ImageStatusAvailable ImageStatus = "available" 67) 68 69// ImageClient is a client for the image API. 70type ImageClient struct { 71 client *Client 72} 73 74// GetByID retrieves an image by its ID. If the image does not exist, nil is returned. 75func (c *ImageClient) GetByID(ctx context.Context, id int) (*Image, *Response, error) { 76 req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/images/%d", id), nil) 77 if err != nil { 78 return nil, nil, err 79 } 80 81 var body schema.ImageGetResponse 82 resp, err := c.client.Do(req, &body) 83 if err != nil { 84 if IsError(err, ErrorCodeNotFound) { 85 return nil, resp, nil 86 } 87 return nil, nil, err 88 } 89 return ImageFromSchema(body.Image), resp, nil 90} 91 92// GetByName retrieves an image by its name. If the image does not exist, nil is returned. 93func (c *ImageClient) GetByName(ctx context.Context, name string) (*Image, *Response, error) { 94 if name == "" { 95 return nil, nil, nil 96 } 97 images, response, err := c.List(ctx, ImageListOpts{Name: name}) 98 if len(images) == 0 { 99 return nil, response, err 100 } 101 return images[0], response, err 102} 103 104// Get retrieves an image by its ID if the input can be parsed as an integer, otherwise it 105// retrieves an image by its name. If the image does not exist, nil is returned. 106func (c *ImageClient) Get(ctx context.Context, idOrName string) (*Image, *Response, error) { 107 if id, err := strconv.Atoi(idOrName); err == nil { 108 return c.GetByID(ctx, int(id)) 109 } 110 return c.GetByName(ctx, idOrName) 111} 112 113// ImageListOpts specifies options for listing images. 114type ImageListOpts struct { 115 ListOpts 116 Type []ImageType 117 BoundTo *Server 118 Name string 119 Sort []string 120 Status []ImageStatus 121 IncludeDeprecated bool 122} 123 124func (l ImageListOpts) values() url.Values { 125 vals := l.ListOpts.values() 126 for _, typ := range l.Type { 127 vals.Add("type", string(typ)) 128 } 129 if l.BoundTo != nil { 130 vals.Add("bound_to", strconv.Itoa(l.BoundTo.ID)) 131 } 132 if l.Name != "" { 133 vals.Add("name", l.Name) 134 } 135 if l.IncludeDeprecated { 136 vals.Add("include_deprecated", strconv.FormatBool(l.IncludeDeprecated)) 137 } 138 for _, sort := range l.Sort { 139 vals.Add("sort", sort) 140 } 141 for _, status := range l.Status { 142 vals.Add("status", string(status)) 143 } 144 return vals 145} 146 147// List returns a list of images for a specific page. 148// 149// Please note that filters specified in opts are not taken into account 150// when their value corresponds to their zero value or when they are empty. 151func (c *ImageClient) List(ctx context.Context, opts ImageListOpts) ([]*Image, *Response, error) { 152 path := "/images?" + opts.values().Encode() 153 req, err := c.client.NewRequest(ctx, "GET", path, nil) 154 if err != nil { 155 return nil, nil, err 156 } 157 158 var body schema.ImageListResponse 159 resp, err := c.client.Do(req, &body) 160 if err != nil { 161 return nil, nil, err 162 } 163 images := make([]*Image, 0, len(body.Images)) 164 for _, i := range body.Images { 165 images = append(images, ImageFromSchema(i)) 166 } 167 return images, resp, nil 168} 169 170// All returns all images. 171func (c *ImageClient) All(ctx context.Context) ([]*Image, error) { 172 return c.AllWithOpts(ctx, ImageListOpts{ListOpts: ListOpts{PerPage: 50}}) 173} 174 175// AllWithOpts returns all images for the given options. 176func (c *ImageClient) AllWithOpts(ctx context.Context, opts ImageListOpts) ([]*Image, error) { 177 allImages := []*Image{} 178 179 err := c.client.all(func(page int) (*Response, error) { 180 opts.Page = page 181 images, resp, err := c.List(ctx, opts) 182 if err != nil { 183 return resp, err 184 } 185 allImages = append(allImages, images...) 186 return resp, nil 187 }) 188 if err != nil { 189 return nil, err 190 } 191 192 return allImages, nil 193} 194 195// Delete deletes an image. 196func (c *ImageClient) Delete(ctx context.Context, image *Image) (*Response, error) { 197 req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/images/%d", image.ID), nil) 198 if err != nil { 199 return nil, err 200 } 201 return c.client.Do(req, nil) 202} 203 204// ImageUpdateOpts specifies options for updating an image. 205type ImageUpdateOpts struct { 206 Description *string 207 Type ImageType 208 Labels map[string]string 209} 210 211// Update updates an image. 212func (c *ImageClient) Update(ctx context.Context, image *Image, opts ImageUpdateOpts) (*Image, *Response, error) { 213 reqBody := schema.ImageUpdateRequest{ 214 Description: opts.Description, 215 } 216 if opts.Type != "" { 217 reqBody.Type = String(string(opts.Type)) 218 } 219 if opts.Labels != nil { 220 reqBody.Labels = &opts.Labels 221 } 222 reqBodyData, err := json.Marshal(reqBody) 223 if err != nil { 224 return nil, nil, err 225 } 226 227 path := fmt.Sprintf("/images/%d", image.ID) 228 req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData)) 229 if err != nil { 230 return nil, nil, err 231 } 232 233 respBody := schema.ImageUpdateResponse{} 234 resp, err := c.client.Do(req, &respBody) 235 if err != nil { 236 return nil, resp, err 237 } 238 return ImageFromSchema(respBody.Image), resp, nil 239} 240 241// ImageChangeProtectionOpts specifies options for changing the resource protection level of an image. 242type ImageChangeProtectionOpts struct { 243 Delete *bool 244} 245 246// ChangeProtection changes the resource protection level of an image. 247func (c *ImageClient) ChangeProtection(ctx context.Context, image *Image, opts ImageChangeProtectionOpts) (*Action, *Response, error) { 248 reqBody := schema.ImageActionChangeProtectionRequest{ 249 Delete: opts.Delete, 250 } 251 reqBodyData, err := json.Marshal(reqBody) 252 if err != nil { 253 return nil, nil, err 254 } 255 256 path := fmt.Sprintf("/images/%d/actions/change_protection", image.ID) 257 req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) 258 if err != nil { 259 return nil, nil, err 260 } 261 262 respBody := schema.ImageActionChangeProtectionResponse{} 263 resp, err := c.client.Do(req, &respBody) 264 if err != nil { 265 return nil, resp, err 266 } 267 return ActionFromSchema(respBody.Action), resp, err 268} 269