diff --git a/volume.go b/volume.go index 5614de6..b913249 100644 --- a/volume.go +++ b/volume.go @@ -41,6 +41,7 @@ type VolumeConfig struct { SizeGigabytes int `json:"size_gb"` Bootable bool `json:"bootable"` VolumeType string `json:"volume_type"` + SnapshotID string `json:"snapshot_id,omitempty"` } // VolumeAttachConfig is the configuration used to attach volume @@ -242,3 +243,56 @@ func (c *Client) DeleteVolume(id string) (*SimpleResponse, error) { return c.DecodeSimpleResponse(resp) } + +// GetVolumeSnapshotByVolumeID retrieves a specific volume snapshot by volume ID and snapshot ID +func (c *Client) GetVolumeSnapshotByVolumeID(volumeID, snapshotID string) (VolumeSnapshot, error) { + resp, err := c.SendGetRequest(fmt.Sprintf("/v2/volumes/%s/snapshots/%s", volumeID, snapshotID)) + if err != nil { + return VolumeSnapshot{}, decodeError(err) + } + var volumeSnapshot = VolumeSnapshot{} + if err := json.NewDecoder(bytes.NewReader(resp)).Decode(&volumeSnapshot); err != nil { + return VolumeSnapshot{}, err + } + return volumeSnapshot, nil +} + +// ListVolumeSnapshotsByVolumeID returns all snapshots for a specific volume by volume ID +func (c *Client) ListVolumeSnapshotsByVolumeID(volumeID string) ([]VolumeSnapshot, error) { + resp, err := c.SendGetRequest(fmt.Sprintf("/v2/volumes/%s/snapshots", volumeID)) + if err != nil { + return nil, decodeError(err) + } + + var volumeSnapshots = make([]VolumeSnapshot, 0) + if err := json.NewDecoder(bytes.NewReader(resp)).Decode(&volumeSnapshots); err != nil { + return nil, err + } + + return volumeSnapshots, nil +} + +// CreateVolumeSnapshot creates a snapshot of a volume +func (c *Client) CreateVolumeSnapshot(volumeID string, config *VolumeSnapshotConfig) (*VolumeSnapshot, error) { + body, err := c.SendPostRequest(fmt.Sprintf("/v2/volumes/%s/snapshots", volumeID), config) + if err != nil { + return nil, decodeError(err) + } + + var result = &VolumeSnapshot{} + if err := json.NewDecoder(bytes.NewReader(body)).Decode(result); err != nil { + return nil, err + } + + return result, nil +} + +// DeleteVolumeAndAllSnapshot deletes a volume and all its snapshots +func (c *Client) DeleteVolumeAndAllSnapshot(volumeID string) (*SimpleResponse, error) { + resp, err := c.SendDeleteRequest(fmt.Sprintf("/v2/volumes/%s?delete_snapshot=true", volumeID)) + if err != nil { + return nil, decodeError(err) + } + + return c.DecodeSimpleResponse(resp) +} diff --git a/volume_snapshot.go b/volume_snapshot.go new file mode 100644 index 0000000..cc95d86 --- /dev/null +++ b/volume_snapshot.go @@ -0,0 +1,65 @@ +package civogo + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// VolumeSnapshot is the point-in-time copy of a Volume +type VolumeSnapshot struct { + Name string `json:"name"` + SnapshotID string `json:"snapshot_id"` + SnapshotDescription string `json:"snapshot_description"` + VolumeID string `json:"volume_id"` + InstanceID string `json:"instance_id,omitempty"` + SourceVolumeName string `json:"source_volume_name"` + RestoreSize int `json:"restore_size"` + State string `json:"state"` + CreationTime string `json:"creation_time,omitempty"` +} + +// VolumeSnapshotConfig is the configuration for creating a new VolumeSnapshot +type VolumeSnapshotConfig struct { + Name string `json:"name"` + Description string `json:"description"` + Region string `json:"region"` +} + +// ListVolumeSnapshots returns all snapshots owned by the calling API account +func (c *Client) ListVolumeSnapshots() ([]VolumeSnapshot, error) { + resp, err := c.SendGetRequest("/v2/snapshots?resource_type=volume") + if err != nil { + return nil, decodeError(err) + } + + var volumeSnapshots = make([]VolumeSnapshot, 0) + if err := json.NewDecoder(bytes.NewReader(resp)).Decode(&volumeSnapshots); err != nil { + return nil, err + } + + return volumeSnapshots, nil +} + +// GetVolumeSnapshot finds a volume by the full ID +func (c *Client) GetVolumeSnapshot(id string) (VolumeSnapshot, error) { + resp, err := c.SendGetRequest(fmt.Sprintf("/v2/snapshots/%s?resource_type=volume", id)) + if err != nil { + return VolumeSnapshot{}, decodeError(err) + } + var volumeSnapshot = VolumeSnapshot{} + if err := json.NewDecoder(bytes.NewReader(resp)).Decode(&volumeSnapshot); err != nil { + return VolumeSnapshot{}, err + } + return volumeSnapshot, nil +} + +// DeleteVolumeSnapshot deletes a volume snapshot +func (c *Client) DeleteVolumeSnapshot(id string) (*SimpleResponse, error) { + resp, err := c.SendDeleteRequest(fmt.Sprintf("/v2/snapshots/%s", id)) + if err != nil { + return nil, decodeError(err) + } + + return c.DecodeSimpleResponse(resp) +} diff --git a/volume_snapshot_test.go b/volume_snapshot_test.go new file mode 100644 index 0000000..c425801 --- /dev/null +++ b/volume_snapshot_test.go @@ -0,0 +1,106 @@ +package civogo + +import ( + "reflect" + "testing" +) + +func TestListVolumeSnapshots(t *testing.T) { + client, server, _ := NewClientForTesting(map[string]string{ + "/v2/snapshots?region=TEST&resource_type=volume": `[{ + "name": "test-snapshot", + "snapshot_id": "12345", + "snapshot_description": "snapshot for test", + "volume_id": "12345", + "source_volume_name": "test-volume", + "instance_id": "ins1234", + "restore_size": 20, + "state": "available", + "creation_time": "2020-01-01T00:00:00Z" + }]`, + }) + defer server.Close() + + got, err := client.ListVolumeSnapshots() + + if err != nil { + t.Errorf("Request returned an error: %s", err) + return + } + + expected := []VolumeSnapshot{ + { + Name: "test-snapshot", + SnapshotID: "12345", + SnapshotDescription: "snapshot for test", + VolumeID: "12345", + SourceVolumeName: "test-volume", + InstanceID: "ins1234", + RestoreSize: 20, + State: "available", + CreationTime: "2020-01-01T00:00:00Z", + }, + } + + if !reflect.DeepEqual(got, expected) { + t.Errorf("Expected %+v, got %+v", expected, got) + } +} + +func TestGetVolumeSnapshot(t *testing.T) { + client, server, _ := NewClientForTesting(map[string]string{ + "/v2/snapshots/snapshot-uuid?region=TEST&resource_type=volume": `{ + "name": "test-snapshot", + "snapshot_id": "snapshot-uuid", + "snapshot_description": "snapshot for testing", + "volume_id": "12345", + "source_volume_name": "test-volume", + "instance_id": "ins1234", + "restore_size": 20, + "state": "available", + "creation_time": "2020-01-01T00:00:00Z" + }`, + }) + defer server.Close() + got, err := client.GetVolumeSnapshot("snapshot-uuid") + + if err != nil { + t.Errorf("Request returned an error: %s", err) + return + } + + expected := VolumeSnapshot{ + Name: "test-snapshot", + SnapshotID: "snapshot-uuid", + SnapshotDescription: "snapshot for testing", + VolumeID: "12345", + SourceVolumeName: "test-volume", + InstanceID: "ins1234", + RestoreSize: 20, + State: "available", + CreationTime: "2020-01-01T00:00:00Z", + } + + if !reflect.DeepEqual(got, expected) { + t.Errorf("Expected %+v, got %+v", expected, got) + } +} + +func TestDeleteVolumeSnapshot(t *testing.T) { + client, server, _ := NewClientForTesting(map[string]string{ + "/v2/snapshots/12346": `{"result": "success"}`, + }) + defer server.Close() + got, err := client.DeleteVolumeSnapshot("12346") + + if err != nil { + t.Errorf("Request returned an error: %s", err) + return + } + + expected := &SimpleResponse{Result: "success"} + + if !reflect.DeepEqual(got, expected) { + t.Errorf("Expected %+v, got %+v", expected, got) + } +} diff --git a/volume_test.go b/volume_test.go index a820c83..2a6ed59 100644 --- a/volume_test.go +++ b/volume_test.go @@ -260,3 +260,136 @@ func TestResizeVolume(t *testing.T) { t.Errorf("Expected %+v, got %+v", expected, got) } } + +func TestCreateVolumeSnapshot(t *testing.T) { + client, server, _ := NewClientForTesting(map[string]string{ + "/v2/volumes/12346/snapshot": `{ + "snapshot_id": "12345", + "name": "test-snapshot", + "snapshot_description": "snapshot for testing", + "volume_id": "12346", + "instance_id": "instance-123", + "source_volume_name": "source-volume", + "state": "available", + "creation_time": "2020-01-01T00:00:00Z" + }`, + }) + defer server.Close() + cfg := &VolumeSnapshotConfig{Name: "my-snapshot"} + got, err := client.CreateVolumeSnapshot("12346", cfg) + if err != nil { + t.Errorf("Request returned an error: %s", err) + return + } + + expected := &VolumeSnapshot{ + SnapshotID: "12345", + Name: "test-snapshot", + SnapshotDescription: "snapshot for testing", + VolumeID: "12346", + InstanceID: "instance-123", + SourceVolumeName: "source-volume", + State: "available", + CreationTime: "2020-01-01T00:00:00Z", + } + if !reflect.DeepEqual(got, expected) { + t.Errorf("Expected %+v, got %+v", expected, got) + } +} + +func TestGetVolumeSnapshotByVolumeID(t *testing.T) { + client, server, _ := NewClientForTesting(map[string]string{ + "/v2/volumes/12346/snapshots/12345": `{ + "snapshot_id": "12345", + "name": "test-snapshot", + "snapshot_description": "snapshot for testing", + "volume_id": "12346", + "instance_id": "instance-123", + "source_volume_name": "source-volume", + "state": "available", + "creation_time": "2020-01-01T00:00:00Z" + }`, + }) + defer server.Close() + + got, err := client.GetVolumeSnapshotByVolumeID("12346", "12345") + if err != nil { + t.Errorf("Request returned an error: %s", err) + return + } + + expected := VolumeSnapshot{ + SnapshotID: "12345", + Name: "test-snapshot", + SnapshotDescription: "snapshot for testing", + VolumeID: "12346", + InstanceID: "instance-123", + SourceVolumeName: "source-volume", + State: "available", + CreationTime: "2020-01-01T00:00:00Z", + } + + if !reflect.DeepEqual(got, expected) { + t.Errorf("Expected %+v, got %+v", expected, got) + } +} + +func TestListVolumeSnapshotsByVolumeID(t *testing.T) { + client, server, _ := NewClientForTesting(map[string]string{ + "/v2/volumes/12346/snapshots": `[{ + "snapshot_id": "12345", + "name": "test-snapshot", + "snapshot_description": "snapshot for testing", + "volume_id": "12346", + "instance_id": "instance-123", + "source_volume_name": "source-volume", + "state": "available", + "creation_time": "2020-01-01T00:00:00Z" + }]`, + }) + defer server.Close() + + got, err := client.ListVolumeSnapshotsByVolumeID("12346") + if err != nil { + t.Errorf("Request returned an error: %s", err) + return + } + + expected := []VolumeSnapshot{ + { + SnapshotID: "12345", + Name: "test-snapshot", + SnapshotDescription: "snapshot for testing", + VolumeID: "12346", + InstanceID: "instance-123", + SourceVolumeName: "source-volume", + State: "available", + CreationTime: "2020-01-01T00:00:00Z", + }, + } + + if !reflect.DeepEqual(got, expected) { + t.Errorf("Expected %+v, got %+v", expected, got) + } +} + +func TestDeleteVolumeAndAllSnapshot(t *testing.T) { + client, server, _ := NewClientForTesting(map[string]string{ + "/v2/volumes/12346?delete_snapshot=true": `{"result": "success"}`, + }) + defer server.Close() + + got, err := client.DeleteVolumeAndAllSnapshot("12346") + if err != nil { + t.Errorf("Request returned an error: %s", err) + return + } + + expected := &SimpleResponse{ + Result: "success", + } + + if !reflect.DeepEqual(got, expected) { + t.Errorf("Expected %+v, got %+v", expected, got) + } +}