Add support for creating custom images and listing images by tag. (#186)
* Add support for creating custom images. * Add support for listing images by tag. * Use http method constants.
This commit is contained in:
parent
8f0fd9c1e6
commit
c4ae66932b
63
images.go
63
images.go
|
@ -16,8 +16,10 @@ type ImagesService interface {
|
||||||
ListDistribution(ctx context.Context, opt *ListOptions) ([]Image, *Response, error)
|
ListDistribution(ctx context.Context, opt *ListOptions) ([]Image, *Response, error)
|
||||||
ListApplication(ctx context.Context, opt *ListOptions) ([]Image, *Response, error)
|
ListApplication(ctx context.Context, opt *ListOptions) ([]Image, *Response, error)
|
||||||
ListUser(ctx context.Context, opt *ListOptions) ([]Image, *Response, error)
|
ListUser(ctx context.Context, opt *ListOptions) ([]Image, *Response, error)
|
||||||
|
ListByTag(ctx context.Context, tag string, opt *ListOptions) ([]Image, *Response, error)
|
||||||
GetByID(context.Context, int) (*Image, *Response, error)
|
GetByID(context.Context, int) (*Image, *Response, error)
|
||||||
GetBySlug(context.Context, string) (*Image, *Response, error)
|
GetBySlug(context.Context, string) (*Image, *Response, error)
|
||||||
|
Create(context.Context, *CustomImageCreateRequest) (*Image, *Response, error)
|
||||||
Update(context.Context, int, *ImageUpdateRequest) (*Image, *Response, error)
|
Update(context.Context, int, *ImageUpdateRequest) (*Image, *Response, error)
|
||||||
Delete(context.Context, int) (*Response, error)
|
Delete(context.Context, int) (*Response, error)
|
||||||
}
|
}
|
||||||
|
@ -32,15 +34,20 @@ var _ ImagesService = &ImagesServiceOp{}
|
||||||
|
|
||||||
// Image represents a DigitalOcean Image
|
// Image represents a DigitalOcean Image
|
||||||
type Image struct {
|
type Image struct {
|
||||||
ID int `json:"id,float64,omitempty"`
|
ID int `json:"id,float64,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
Distribution string `json:"distribution,omitempty"`
|
Distribution string `json:"distribution,omitempty"`
|
||||||
Slug string `json:"slug,omitempty"`
|
Slug string `json:"slug,omitempty"`
|
||||||
Public bool `json:"public,omitempty"`
|
Public bool `json:"public,omitempty"`
|
||||||
Regions []string `json:"regions,omitempty"`
|
Regions []string `json:"regions,omitempty"`
|
||||||
MinDiskSize int `json:"min_disk_size,omitempty"`
|
MinDiskSize int `json:"min_disk_size,omitempty"`
|
||||||
Created string `json:"created_at,omitempty"`
|
SizeGigaBytes float64 `json:"size_gigabytes,omitempty"`
|
||||||
|
Created string `json:"created_at,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Tags []string `json:"tags,omitempty"`
|
||||||
|
Status string `json:"status,omitempty"`
|
||||||
|
ErrorMessage string `json:"error_message,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImageUpdateRequest represents a request to update an image.
|
// ImageUpdateRequest represents a request to update an image.
|
||||||
|
@ -48,6 +55,16 @@ type ImageUpdateRequest struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CustomImageCreateRequest represents a request to create a custom image.
|
||||||
|
type CustomImageCreateRequest struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
Region string `json:"region"`
|
||||||
|
Distribution string `json:"distribution,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Tags []string `json:"tags,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type imageRoot struct {
|
type imageRoot struct {
|
||||||
Image *Image
|
Image *Image
|
||||||
}
|
}
|
||||||
|
@ -60,6 +77,7 @@ type imagesRoot struct {
|
||||||
type listImageOptions struct {
|
type listImageOptions struct {
|
||||||
Private bool `url:"private,omitempty"`
|
Private bool `url:"private,omitempty"`
|
||||||
Type string `url:"type,omitempty"`
|
Type string `url:"type,omitempty"`
|
||||||
|
Tag string `url:"tag_name,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i Image) String() string {
|
func (i Image) String() string {
|
||||||
|
@ -89,6 +107,12 @@ func (s *ImagesServiceOp) ListUser(ctx context.Context, opt *ListOptions) ([]Ima
|
||||||
return s.list(ctx, opt, &listOpt)
|
return s.list(ctx, opt, &listOpt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListByTag lists all images with a specific tag applied.
|
||||||
|
func (s *ImagesServiceOp) ListByTag(ctx context.Context, tag string, opt *ListOptions) ([]Image, *Response, error) {
|
||||||
|
listOpt := listImageOptions{Tag: tag}
|
||||||
|
return s.list(ctx, opt, &listOpt)
|
||||||
|
}
|
||||||
|
|
||||||
// GetByID retrieves an image by id.
|
// GetByID retrieves an image by id.
|
||||||
func (s *ImagesServiceOp) GetByID(ctx context.Context, imageID int) (*Image, *Response, error) {
|
func (s *ImagesServiceOp) GetByID(ctx context.Context, imageID int) (*Image, *Response, error) {
|
||||||
if imageID < 1 {
|
if imageID < 1 {
|
||||||
|
@ -107,6 +131,25 @@ func (s *ImagesServiceOp) GetBySlug(ctx context.Context, slug string) (*Image, *
|
||||||
return s.get(ctx, interface{}(slug))
|
return s.get(ctx, interface{}(slug))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ImagesServiceOp) Create(ctx context.Context, createRequest *CustomImageCreateRequest) (*Image, *Response, error) {
|
||||||
|
if createRequest == nil {
|
||||||
|
return nil, nil, NewArgError("createRequest", "cannot be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest(ctx, http.MethodPost, imageBasePath, createRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
root := new(imageRoot)
|
||||||
|
resp, err := s.client.Do(ctx, req, root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return root.Image, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
// Update an image name.
|
// Update an image name.
|
||||||
func (s *ImagesServiceOp) Update(ctx context.Context, imageID int, updateRequest *ImageUpdateRequest) (*Image, *Response, error) {
|
func (s *ImagesServiceOp) Update(ctx context.Context, imageID int, updateRequest *ImageUpdateRequest) (*Image, *Response, error) {
|
||||||
if imageID < 1 {
|
if imageID < 1 {
|
||||||
|
@ -118,7 +161,7 @@ func (s *ImagesServiceOp) Update(ctx context.Context, imageID int, updateRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
path := fmt.Sprintf("%s/%d", imageBasePath, imageID)
|
path := fmt.Sprintf("%s/%d", imageBasePath, imageID)
|
||||||
req, err := s.client.NewRequest(ctx, "PUT", path, updateRequest)
|
req, err := s.client.NewRequest(ctx, http.MethodPut, path, updateRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,6 +104,32 @@ func TestImages_ListUser(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestImages_ListByTag(t *testing.T) {
|
||||||
|
setup()
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
mux.HandleFunc("/v2/images", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testMethod(t, r, http.MethodGet)
|
||||||
|
expected := "foo"
|
||||||
|
actual := r.URL.Query().Get("tag_name")
|
||||||
|
if actual != expected {
|
||||||
|
t.Errorf("'tag_name' query = %v, expected %v", actual, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(w, `{"images":[{"id":1},{"id":2}]}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
images, _, err := client.Images.ListByTag(ctx, "foo", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Images.ListByTag returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []Image{{ID: 1}, {ID: 2}}
|
||||||
|
if !reflect.DeepEqual(images, expected) {
|
||||||
|
t.Errorf("Images.ListByTag returned %+v, expected %+v", images, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestImages_ListImagesMultiplePages(t *testing.T) {
|
func TestImages_ListImagesMultiplePages(t *testing.T) {
|
||||||
setup()
|
setup()
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
@ -191,6 +217,52 @@ func TestImages_GetImageBySlug(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestImages_Create(t *testing.T) {
|
||||||
|
setup()
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
createRequest := &CustomImageCreateRequest{
|
||||||
|
Name: "my-new-image",
|
||||||
|
Url: "http://example.com/distro-amd64.img",
|
||||||
|
Region: "nyc3",
|
||||||
|
Distribution: "Ubuntu",
|
||||||
|
Description: "My new custom image",
|
||||||
|
Tags: []string{"foo", "bar"},
|
||||||
|
}
|
||||||
|
|
||||||
|
mux.HandleFunc("/v2/images", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
expected := map[string]interface{}{
|
||||||
|
"name": "my-new-image",
|
||||||
|
"url": "http://example.com/distro-amd64.img",
|
||||||
|
"region": "nyc3",
|
||||||
|
"distribution": "Ubuntu",
|
||||||
|
"description": "My new custom image",
|
||||||
|
"tags": []interface{}{"foo", "bar"},
|
||||||
|
}
|
||||||
|
|
||||||
|
var v map[string]interface{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&v)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("decode json: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(v, expected) {
|
||||||
|
t.Errorf("Request body\n got=%#v\nwant=%#v", v, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(w, `{"image": {"id": 1,"created_at": "2018-09-20T19:28:00Z","description": "A custom image","distribution": "Ubuntu","error_message": "","regions": [],"type": "custom","tags":["foo","bar"],"status": "NEW"}}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
image, _, err := client.Images.Create(ctx, createRequest)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Images.Create returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if id := image.ID; id != 1 {
|
||||||
|
t.Errorf("expected id '%d', received '%d'", 1, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestImages_Update(t *testing.T) {
|
func TestImages_Update(t *testing.T) {
|
||||||
setup()
|
setup()
|
||||||
defer teardown()
|
defer teardown()
|
||||||
|
@ -243,19 +315,20 @@ func TestImages_Destroy(t *testing.T) {
|
||||||
|
|
||||||
func TestImage_String(t *testing.T) {
|
func TestImage_String(t *testing.T) {
|
||||||
image := &Image{
|
image := &Image{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Name: "Image",
|
Name: "Image",
|
||||||
Type: "snapshot",
|
Type: "snapshot",
|
||||||
Distribution: "Ubuntu",
|
Distribution: "Ubuntu",
|
||||||
Slug: "image",
|
Slug: "image",
|
||||||
Public: true,
|
Public: true,
|
||||||
Regions: []string{"one", "two"},
|
Regions: []string{"one", "two"},
|
||||||
MinDiskSize: 20,
|
MinDiskSize: 20,
|
||||||
Created: "2013-11-27T09:24:55Z",
|
SizeGigaBytes: 2.36,
|
||||||
|
Created: "2013-11-27T09:24:55Z",
|
||||||
}
|
}
|
||||||
|
|
||||||
stringified := image.String()
|
stringified := image.String()
|
||||||
expected := `godo.Image{ID:1, Name:"Image", Type:"snapshot", Distribution:"Ubuntu", Slug:"image", Public:true, Regions:["one" "two"], MinDiskSize:20, Created:"2013-11-27T09:24:55Z"}`
|
expected := `godo.Image{ID:1, Name:"Image", Type:"snapshot", Distribution:"Ubuntu", Slug:"image", Public:true, Regions:["one" "two"], MinDiskSize:20, SizeGigaBytes:2.36, Created:"2013-11-27T09:24:55Z", Description:"", Status:"", ErrorMessage:""}`
|
||||||
if expected != stringified {
|
if expected != stringified {
|
||||||
t.Errorf("Image.String returned %+v, expected %+v", stringified, expected)
|
t.Errorf("Image.String returned %+v, expected %+v", stringified, expected)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue