1// 2// Copyright (c) 2018, Joyent, Inc. All rights reserved. 3// 4// This Source Code Form is subject to the terms of the Mozilla Public 5// License, v. 2.0. If a copy of the MPL was not distributed with this 6// file, You can obtain one at http://mozilla.org/MPL/2.0/. 7// 8 9package compute 10 11import ( 12 "context" 13 "encoding/json" 14 "net/http" 15 "net/url" 16 "path" 17 "time" 18 19 "github.com/joyent/triton-go/client" 20 "github.com/pkg/errors" 21) 22 23type ImagesClient struct { 24 client *client.Client 25} 26 27type ImageFile struct { 28 Compression string `json:"compression"` 29 SHA1 string `json:"sha1"` 30 Size int64 `json:"size"` 31} 32 33type Image struct { 34 ID string `json:"id"` 35 Name string `json:"name"` 36 OS string `json:"os"` 37 Description string `json:"description"` 38 Version string `json:"version"` 39 Type string `json:"type"` 40 Requirements map[string]interface{} `json:"requirements"` 41 Homepage string `json:"homepage"` 42 Files []*ImageFile `json:"files"` 43 PublishedAt time.Time `json:"published_at"` 44 Owner string `json:"owner"` 45 Public bool `json:"public"` 46 State string `json:"state"` 47 Tags map[string]string `json:"tags"` 48 EULA string `json:"eula"` 49 ACL []string `json:"acl"` 50} 51 52type ListImagesInput struct { 53 Name string 54 OS string 55 Version string 56 Public bool 57 State string 58 Owner string 59 Type string 60} 61 62func (c *ImagesClient) List(ctx context.Context, input *ListImagesInput) ([]*Image, error) { 63 fullPath := path.Join("/", c.client.AccountName, "images") 64 65 query := &url.Values{} 66 if input.Name != "" { 67 query.Set("name", input.Name) 68 } 69 if input.OS != "" { 70 query.Set("os", input.OS) 71 } 72 if input.Version != "" { 73 query.Set("version", input.Version) 74 } 75 if input.Public { 76 query.Set("public", "true") 77 } 78 if input.State != "" { 79 query.Set("state", input.State) 80 } 81 if input.Owner != "" { 82 query.Set("owner", input.Owner) 83 } 84 if input.Type != "" { 85 query.Set("type", input.Type) 86 } 87 88 reqInputs := client.RequestInput{ 89 Method: http.MethodGet, 90 Path: fullPath, 91 Query: query, 92 } 93 respReader, err := c.client.ExecuteRequestURIParams(ctx, reqInputs) 94 if respReader != nil { 95 defer respReader.Close() 96 } 97 if err != nil { 98 return nil, errors.Wrap(err, "unable to list images") 99 } 100 101 var result []*Image 102 decoder := json.NewDecoder(respReader) 103 if err = decoder.Decode(&result); err != nil { 104 return nil, errors.Wrap(err, "unable to decode list images response") 105 } 106 107 return result, nil 108} 109 110type GetImageInput struct { 111 ImageID string 112} 113 114func (c *ImagesClient) Get(ctx context.Context, input *GetImageInput) (*Image, error) { 115 fullPath := path.Join("/", c.client.AccountName, "images", input.ImageID) 116 reqInputs := client.RequestInput{ 117 Method: http.MethodGet, 118 Path: fullPath, 119 } 120 respReader, err := c.client.ExecuteRequest(ctx, reqInputs) 121 if respReader != nil { 122 defer respReader.Close() 123 } 124 if err != nil { 125 return nil, errors.Wrap(err, "unable to get image") 126 } 127 128 var result *Image 129 decoder := json.NewDecoder(respReader) 130 if err = decoder.Decode(&result); err != nil { 131 return nil, errors.Wrap(err, "unable to decode get image response") 132 } 133 134 return result, nil 135} 136 137type DeleteImageInput struct { 138 ImageID string 139} 140 141func (c *ImagesClient) Delete(ctx context.Context, input *DeleteImageInput) error { 142 fullPath := path.Join("/", c.client.AccountName, "images", input.ImageID) 143 reqInputs := client.RequestInput{ 144 Method: http.MethodDelete, 145 Path: fullPath, 146 } 147 respReader, err := c.client.ExecuteRequest(ctx, reqInputs) 148 if respReader != nil { 149 defer respReader.Close() 150 } 151 if err != nil { 152 return errors.Wrap(err, "unable to delete image") 153 } 154 155 return nil 156} 157 158type ExportImageInput struct { 159 ImageID string 160 MantaPath string 161} 162 163type MantaLocation struct { 164 MantaURL string `json:"manta_url"` 165 ImagePath string `json:"image_path"` 166 ManifestPath string `json:"manifest_path"` 167} 168 169func (c *ImagesClient) Export(ctx context.Context, input *ExportImageInput) (*MantaLocation, error) { 170 fullPath := path.Join("/", c.client.AccountName, "images", input.ImageID) 171 query := &url.Values{} 172 query.Set("action", "export") 173 174 reqInputs := client.RequestInput{ 175 Method: http.MethodPost, 176 Path: fullPath, 177 Query: query, 178 } 179 respReader, err := c.client.ExecuteRequestURIParams(ctx, reqInputs) 180 if respReader != nil { 181 defer respReader.Close() 182 } 183 if err != nil { 184 return nil, errors.Wrap(err, "unable to export image") 185 } 186 187 var result *MantaLocation 188 decoder := json.NewDecoder(respReader) 189 if err = decoder.Decode(&result); err != nil { 190 return nil, errors.Wrap(err, "unable to decode export image response") 191 } 192 193 return result, nil 194} 195 196type CreateImageFromMachineInput struct { 197 MachineID string `json:"machine"` 198 Name string `json:"name"` 199 Version string `json:"version,omitempty"` 200 Description string `json:"description,omitempty"` 201 HomePage string `json:"homepage,omitempty"` 202 EULA string `json:"eula,omitempty"` 203 ACL []string `json:"acl,omitempty"` 204 Tags map[string]string `json:"tags,omitempty"` 205} 206 207func (c *ImagesClient) CreateFromMachine(ctx context.Context, input *CreateImageFromMachineInput) (*Image, error) { 208 fullPath := path.Join("/", c.client.AccountName, "images") 209 reqInputs := client.RequestInput{ 210 Method: http.MethodPost, 211 Path: fullPath, 212 Body: input, 213 } 214 respReader, err := c.client.ExecuteRequest(ctx, reqInputs) 215 if respReader != nil { 216 defer respReader.Close() 217 } 218 if err != nil { 219 return nil, errors.Wrap(err, "unable to create machine from image") 220 } 221 222 var result *Image 223 decoder := json.NewDecoder(respReader) 224 if err = decoder.Decode(&result); err != nil { 225 return nil, errors.Wrap(err, "unable to decode create machine from image response") 226 } 227 228 return result, nil 229} 230 231type UpdateImageInput struct { 232 ImageID string `json:"-"` 233 Name string `json:"name,omitempty"` 234 Version string `json:"version,omitempty"` 235 Description string `json:"description,omitempty"` 236 HomePage string `json:"homepage,omitempty"` 237 EULA string `json:"eula,omitempty"` 238 ACL []string `json:"acl,omitempty"` 239 Tags map[string]string `json:"tags,omitempty"` 240} 241 242func (c *ImagesClient) Update(ctx context.Context, input *UpdateImageInput) (*Image, error) { 243 fullPath := path.Join("/", c.client.AccountName, "images", input.ImageID) 244 query := &url.Values{} 245 query.Set("action", "update") 246 247 reqInputs := client.RequestInput{ 248 Method: http.MethodPost, 249 Path: fullPath, 250 Query: query, 251 Body: input, 252 } 253 respReader, err := c.client.ExecuteRequestURIParams(ctx, reqInputs) 254 if respReader != nil { 255 defer respReader.Close() 256 } 257 if err != nil { 258 return nil, errors.Wrap(err, "unable to update image") 259 } 260 261 var result *Image 262 decoder := json.NewDecoder(respReader) 263 if err = decoder.Decode(&result); err != nil { 264 return nil, errors.Wrap(err, "unable to decode update image response") 265 } 266 267 return result, nil 268} 269