Merge pull request #108 from digitalocean/feature/snapshots

Add snapshots endpoints.
This commit is contained in:
Phillip Baker 2016-11-29 22:58:31 -05:00 committed by GitHub
commit bec8984b6b
6 changed files with 336 additions and 55 deletions

View File

@ -131,7 +131,7 @@ type kernelsRoot struct {
Links *Links `json:"links"`
}
type snapshotsRoot struct {
type dropletSnapshotsRoot struct {
Snapshots []Image `json:"snapshots,omitempty"`
Links *Links `json:"links"`
}
@ -509,7 +509,7 @@ func (s *DropletsServiceOp) Snapshots(dropletID int, opt *ListOptions) ([]Image,
return nil, nil, err
}
root := new(snapshotsRoot)
root := new(dropletSnapshotsRoot)
resp, err := s.client.Do(req, root)
if err != nil {
return nil, resp, err

View File

@ -55,6 +55,7 @@ type Client struct {
Sizes SizesService
FloatingIPs FloatingIPsService
FloatingIPActions FloatingIPActionsService
Snapshots SnapshotsService
Storage StorageService
StorageActions StorageActionsService
Tags TagsService
@ -155,13 +156,14 @@ func NewClient(httpClient *http.Client) *Client {
c.Domains = &DomainsServiceOp{client: c}
c.Droplets = &DropletsServiceOp{client: c}
c.DropletActions = &DropletActionsServiceOp{client: c}
c.FloatingIPs = &FloatingIPsServiceOp{client: c}
c.FloatingIPActions = &FloatingIPActionsServiceOp{client: c}
c.Images = &ImagesServiceOp{client: c}
c.ImageActions = &ImageActionsServiceOp{client: c}
c.Keys = &KeysServiceOp{client: c}
c.Regions = &RegionsServiceOp{client: c}
c.Snapshots = &SnapshotsServiceOp{client: c}
c.Sizes = &SizesServiceOp{client: c}
c.FloatingIPs = &FloatingIPsServiceOp{client: c}
c.FloatingIPActions = &FloatingIPActionsServiceOp{client: c}
c.Storage = &StorageServiceOp{client: c}
c.StorageActions = &StorageActionsServiceOp{client: c}
c.Tags = &TagsServiceOp{client: c}

136
snapshots.go Normal file
View File

@ -0,0 +1,136 @@
package godo
import "fmt"
const snapshotBasePath = "v2/snapshots"
// SnapshotsService is an interface for interfacing with the snapshots
// endpoints of the DigitalOcean API
// See: https://developers.digitalocean.com/documentation/v2#snapshots
type SnapshotsService interface {
List(*ListOptions) ([]Snapshot, *Response, error)
ListVolume(*ListOptions) ([]Snapshot, *Response, error)
ListDroplet(*ListOptions) ([]Snapshot, *Response, error)
Get(string) (*Snapshot, *Response, error)
Delete(string) (*Response, error)
}
// SnapshotsServiceOp handles communication with the snapshot related methods of the
// DigitalOcean API.
type SnapshotsServiceOp struct {
client *Client
}
var _ SnapshotsService = &SnapshotsServiceOp{}
// Snapshot represents a DigitalOcean Snapshot
type Snapshot struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
ResourceID string `json:"resource_id,omitempty"`
ResourceType string `json:"resource_type,omitempty"`
Regions []string `json:"regions,omitempty"`
MinDiskSize int `json:"min_disk_size,omitempty"`
SizeGigaBytes int `json:"size_gigabytes,omitempty"`
Created string `json:"created_at,omitempty"`
}
type snapshotRoot struct {
Snapshot *Snapshot `json:"snapshot"`
}
type snapshotsRoot struct {
Snapshots []Snapshot `json:"snapshots"`
Links *Links `json:"links,omitempty"`
}
type listSnapshotOptions struct {
ResourceType string `url:"resource_type,omitempty"`
}
func (s Snapshot) String() string {
return Stringify(s)
}
// List lists all the snapshots available.
func (s *SnapshotsServiceOp) List(opt *ListOptions) ([]Snapshot, *Response, error) {
return s.list(opt, nil)
}
// ListDroplet lists all the Droplet snapshots.
func (s *SnapshotsServiceOp) ListDroplet(opt *ListOptions) ([]Snapshot, *Response, error) {
listOpt := listSnapshotOptions{ResourceType: "droplet"}
return s.list(opt, &listOpt)
}
// ListVolume lists all the volume snapshots.
func (s *SnapshotsServiceOp) ListVolume(opt *ListOptions) ([]Snapshot, *Response, error) {
listOpt := listSnapshotOptions{ResourceType: "volume"}
return s.list(opt, &listOpt)
}
// GetByID retrieves an snapshot by id.
func (s *SnapshotsServiceOp) Get(snapshotID string) (*Snapshot, *Response, error) {
return s.get(interface{}(snapshotID))
}
// Delete an snapshot.
func (s *SnapshotsServiceOp) Delete(snapshotID string) (*Response, error) {
path := fmt.Sprintf("%s/%s", snapshotBasePath, snapshotID)
req, err := s.client.NewRequest("DELETE", path, nil)
if err != nil {
return nil, err
}
resp, err := s.client.Do(req, nil)
return resp, err
}
// Helper method for getting an individual snapshot
func (s *SnapshotsServiceOp) get(ID interface{}) (*Snapshot, *Response, error) {
path := fmt.Sprintf("%s/%v", snapshotBasePath, ID)
req, err := s.client.NewRequest("GET", path, nil)
if err != nil {
return nil, nil, err
}
root := new(snapshotRoot)
resp, err := s.client.Do(req, root)
if err != nil {
return nil, resp, err
}
return root.Snapshot, resp, err
}
// Helper method for listing snapshots
func (s *SnapshotsServiceOp) list(opt *ListOptions, listOpt *listSnapshotOptions) ([]Snapshot, *Response, error) {
path := snapshotBasePath
path, err := addOptions(path, opt)
if err != nil {
return nil, nil, err
}
path, err = addOptions(path, listOpt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", path, nil)
if err != nil {
return nil, nil, err
}
root := new(snapshotsRoot)
resp, err := s.client.Do(req, root)
if err != nil {
return nil, resp, err
}
if l := root.Links; l != nil {
resp.Links = l
}
return root.Snapshots, resp, err
}

179
snapshots_test.go Normal file
View File

@ -0,0 +1,179 @@
package godo
import (
"fmt"
"net/http"
"reflect"
"testing"
)
func TestSnapshots_List(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/snapshots", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"snapshots":[{"id":"1"},{"id":"2"}]}`)
})
snapshots, _, err := client.Snapshots.List(nil)
if err != nil {
t.Errorf("Snapshots.List returned error: %v", err)
}
expected := []Snapshot{{ID: "1"}, {ID: "2"}}
if !reflect.DeepEqual(snapshots, expected) {
t.Errorf("Snapshots.List returned %+v, expected %+v", snapshots, expected)
}
}
func TestSnapshots_ListVolume(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/snapshots", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
expected := "volume"
actual := r.URL.Query().Get("resource_type")
if actual != expected {
t.Errorf("'type' query = %v, expected %v", actual, expected)
}
fmt.Fprint(w, `{"snapshots":[{"id":"1"},{"id":"2"}]}`)
})
snapshots, _, err := client.Snapshots.ListVolume(nil)
if err != nil {
t.Errorf("Snapshots.ListVolume returned error: %v", err)
}
expected := []Snapshot{{ID: "1"}, {ID: "2"}}
if !reflect.DeepEqual(snapshots, expected) {
t.Errorf("Snapshots.ListVolume returned %+v, expected %+v", snapshots, expected)
}
}
func TestSnapshots_ListDroplet(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/snapshots", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
expected := "droplet"
actual := r.URL.Query().Get("resource_type")
if actual != expected {
t.Errorf("'resource_type' query = %v, expected %v", actual, expected)
}
fmt.Fprint(w, `{"snapshots":[{"id":"1"},{"id":"2"}]}`)
})
snapshots, _, err := client.Snapshots.ListDroplet(nil)
if err != nil {
t.Errorf("Snapshots.ListDroplet returned error: %v", err)
}
expected := []Snapshot{{ID: "1"}, {ID: "2"}}
if !reflect.DeepEqual(snapshots, expected) {
t.Errorf("Snapshots.ListDroplet returned %+v, expected %+v", snapshots, expected)
}
}
func TestSnapshots_ListSnapshotsMultiplePages(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/snapshots", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"snapshots": [{"id":"1"},{"id":"2"}], "links":{"pages":{"next":"http://example.com/v2/snapshots/?page=2"}}}`)
})
_, resp, err := client.Snapshots.List(&ListOptions{Page: 2})
if err != nil {
t.Fatal(err)
}
checkCurrentPage(t, resp, 1)
}
func TestSnapshots_RetrievePageByNumber(t *testing.T) {
setup()
defer teardown()
jBlob := `
{
"snapshots": [{"id":"1"},{"id":"2"}],
"links":{
"pages":{
"next":"http://example.com/v2/snapshots/?page=3",
"prev":"http://example.com/v2/snapshots/?page=1",
"last":"http://example.com/v2/snapshots/?page=3",
"first":"http://example.com/v2/snapshots/?page=1"
}
}
}`
mux.HandleFunc("/v2/snapshots", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, jBlob)
})
opt := &ListOptions{Page: 2}
_, resp, err := client.Snapshots.List(opt)
if err != nil {
t.Fatal(err)
}
checkCurrentPage(t, resp, 2)
}
func TestSnapshots_GetSnapshotByID(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/snapshots/12345", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"snapshot":{"id":"12345"}}`)
})
snapshots, _, err := client.Snapshots.Get("12345")
if err != nil {
t.Errorf("Snapshot.GetByID returned error: %v", err)
}
expected := &Snapshot{ID: "12345"}
if !reflect.DeepEqual(snapshots, expected) {
t.Errorf("Snapshots.GetByID returned %+v, expected %+v", snapshots, expected)
}
}
func TestSnapshots_Destroy(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v2/snapshots/12345", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
})
_, err := client.Snapshots.Delete("12345")
if err != nil {
t.Errorf("Snapshot.Delete returned error: %v", err)
}
}
func TestSnapshot_String(t *testing.T) {
snapshot := &Snapshot{
ID: "1",
Name: "Snapsh176ot",
ResourceID: "0",
ResourceType: "droplet",
Regions: []string{"one"},
MinDiskSize: 20,
SizeGigaBytes: 0,
Created: "2013-11-27T09:24:55Z",
}
stringified := snapshot.String()
expected := `godo.Snapshot{ID:"1", Name:"Snapsh176ot", ResourceID:"0", ResourceType:"droplet", Regions:["one"], MinDiskSize:20, SizeGigaBytes:0, Created:"2013-11-27T09:24:55Z"}`
if expected != stringified {
t.Errorf("Snapshot.String returned %+v, expected %+v", stringified, expected)
}
}

View File

@ -150,27 +150,6 @@ func (svc *StorageServiceOp) DeleteVolume(id string) (*Response, error) {
return svc.client.Do(req, nil)
}
// Snapshot represents a Digital Ocean block store snapshot.
type Snapshot struct {
ID string `json:"id"`
VolumeID string `json:"volume_id"`
Region *Region `json:"region"`
Name string `json:"name"`
SizeGigaBytes int64 `json:"size_gigabytes"`
Description string `json:"description"`
CreatedAt time.Time `json:"created_at"`
}
type storageSnapsRoot struct {
Snapshots []Snapshot `json:"snapshots"`
Links *Links `json:"links"`
}
type storageSnapRoot struct {
Snapshot *Snapshot `json:"snapshot"`
Links *Links `json:"links,omitempty"`
}
// SnapshotCreateRequest represents a request to create a block store
// volume.
type SnapshotCreateRequest struct {
@ -192,7 +171,7 @@ func (svc *StorageServiceOp) ListSnapshots(volumeID string, opt *ListOptions) ([
return nil, nil, err
}
root := new(storageSnapsRoot)
root := new(snapshotsRoot)
resp, err := svc.client.Do(req, root)
if err != nil {
return nil, resp, err
@ -214,7 +193,7 @@ func (svc *StorageServiceOp) CreateSnapshot(createRequest *SnapshotCreateRequest
return nil, nil, err
}
root := new(storageSnapRoot)
root := new(snapshotRoot)
resp, err := svc.client.Do(req, root)
if err != nil {
return nil, resp, err
@ -231,7 +210,7 @@ func (svc *StorageServiceOp) GetSnapshot(id string) (*Snapshot, *Response, error
return nil, nil, err
}
root := new(storageSnapRoot)
root := new(snapshotRoot)
resp, err := svc.client.Do(req, root)
if err != nil {
return nil, resp, err

View File

@ -204,20 +204,16 @@ func TestStorageSnapshots_ListStorageSnapshots(t *testing.T) {
{
"snapshots": [
{
"region": {"slug": "nyc3"},
"regions": ["nyc3"],
"id": "80d414c6-295e-4e3a-ac58-eb9456c1e1d1",
"volume_id": "98d414c6-295e-4e3a-ac58-eb9456c1e1d1",
"name": "my snapshot",
"description": "my description",
"size_gigabytes": 100,
"created_at": "2002-10-02T15:00:00.05Z"
},
{
"region": {"slug": "nyc3"},
"regions": ["nyc3"],
"id": "96d414c6-295e-4e3a-ac59-eb9456c1e1d1",
"volume_id": "98d414c6-295e-4e3a-ac58-eb9456c1e1d1",
"name": "my other snapshot",
"description": "my other description",
"size_gigabytes": 100,
"created_at": "2012-10-03T15:00:01.05Z"
}
@ -245,22 +241,18 @@ func TestStorageSnapshots_ListStorageSnapshots(t *testing.T) {
expected := []Snapshot{
{
Region: &Region{Slug: "nyc3"},
Regions: []string{"nyc3"},
ID: "80d414c6-295e-4e3a-ac58-eb9456c1e1d1",
VolumeID: "98d414c6-295e-4e3a-ac58-eb9456c1e1d1",
Name: "my snapshot",
Description: "my description",
SizeGigaBytes: 100,
CreatedAt: time.Date(2002, 10, 02, 15, 00, 00, 50000000, time.UTC),
Created: "2002-10-02T15:00:00.05Z",
},
{
Region: &Region{Slug: "nyc3"},
Regions: []string{"nyc3"},
ID: "96d414c6-295e-4e3a-ac59-eb9456c1e1d1",
VolumeID: "98d414c6-295e-4e3a-ac58-eb9456c1e1d1",
Name: "my other snapshot",
Description: "my other description",
SizeGigaBytes: 100,
CreatedAt: time.Date(2012, 10, 03, 15, 00, 01, 50000000, time.UTC),
Created: "2012-10-03T15:00:01.05Z",
},
}
if !reflect.DeepEqual(volumes, expected) {
@ -272,21 +264,17 @@ func TestStorageSnapshots_Get(t *testing.T) {
setup()
defer teardown()
want := &Snapshot{
Region: &Region{Slug: "nyc3"},
Regions: []string{"nyc3"},
ID: "80d414c6-295e-4e3a-ac58-eb9456c1e1d1",
VolumeID: "98d414c6-295e-4e3a-ac58-eb9456c1e1d1",
Name: "my snapshot",
Description: "my description",
SizeGigaBytes: 100,
CreatedAt: time.Date(2002, 10, 02, 15, 00, 00, 50000000, time.UTC),
Created: "2002-10-02T15:00:00.05Z",
}
jBlob := `{
"snapshot":{
"region": {"slug": "nyc3"},
"regions": ["nyc3"],
"id": "80d414c6-295e-4e3a-ac58-eb9456c1e1d1",
"volume_id": "98d414c6-295e-4e3a-ac58-eb9456c1e1d1",
"name": "my snapshot",
"description": "my description",
"size_gigabytes": 100,
"created_at": "2002-10-02T15:00:00.05Z"
},
@ -326,19 +314,16 @@ func TestStorageSnapshots_Create(t *testing.T) {
}
want := &Snapshot{
Region: &Region{Slug: "nyc3"},
Regions: []string{"nyc3"},
ID: "80d414c6-295e-4e3a-ac58-eb9456c1e1d1",
VolumeID: "98d414c6-295e-4e3a-ac58-eb9456c1e1d1",
Name: "my snapshot",
Description: "my description",
SizeGigaBytes: 100,
CreatedAt: time.Date(2002, 10, 02, 15, 00, 00, 50000000, time.UTC),
Created: "2002-10-02T15:00:00.05Z",
}
jBlob := `{
"snapshot":{
"region": {"slug": "nyc3"},
"regions": ["nyc3"],
"id": "80d414c6-295e-4e3a-ac58-eb9456c1e1d1",
"volume_id": "98d414c6-295e-4e3a-ac58-eb9456c1e1d1",
"name": "my snapshot",
"description": "my description",
"size_gigabytes": 100,