diff --git a/cloudscale.go b/cloudscale.go index 823421a..1f57487 100644 --- a/cloudscale.go +++ b/cloudscale.go @@ -37,6 +37,7 @@ type Client struct { Regions RegionService Servers ServerService Volumes VolumeService + VolumeSnapshots VolumeSnapshotService Networks NetworkService Subnets SubnetService FloatingIPs FloatingIPsService @@ -88,10 +89,14 @@ func NewClient(httpClient *http.Client) *Client { client: c, path: floatingIPsBasePath, } - c.Volumes = GenericServiceOperations[Volume, VolumeRequest, VolumeRequest]{ + c.Volumes = GenericServiceOperations[Volume, VolumeCreateRequest, VolumeUpdateRequest]{ client: c, path: volumeBasePath, } + c.VolumeSnapshots = GenericServiceOperations[VolumeSnapshot, VolumeSnapshotCreateRequest, VolumeSnapshotUpdateRequest]{ + client: c, + path: volumeSnapshotsBasePath, + } c.ServerGroups = GenericServiceOperations[ServerGroup, ServerGroupRequest, ServerGroupRequest]{ client: c, path: serverGroupsBasePath, diff --git a/test/integration/cloudscale_test.go b/test/integration/cloudscale_test.go index 4bfd5c1..223d465 100644 --- a/test/integration/cloudscale_test.go +++ b/test/integration/cloudscale_test.go @@ -49,6 +49,7 @@ func TestMain(m *testing.M) { foundResource := false foundResource = foundResource || DeleteRemainingServer() foundResource = foundResource || DeleteRemainingServerGroups() + foundResource = foundResource || DeleteRemainingVolumeSnapshots() foundResource = foundResource || DeleteRemainingVolumes() foundResource = foundResource || DeleteRemainingSubnets() foundResource = foundResource || DeleteRemainingNetworks() @@ -106,6 +107,28 @@ func DeleteRemainingServerGroups() bool { return foundResource } +func DeleteRemainingVolumeSnapshots() bool { + foundResource := false + + snapshots, err := client.VolumeSnapshots.List(context.Background()) + if err != nil { + log.Fatalf("VolumeSnapshots.List returned error %s\n", err) + } + + for _, snapshot := range snapshots { + if strings.HasPrefix(snapshot.Name, testRunPrefix) { + foundResource = true + log.Printf("Found not deleted snapshot: %s (%s)\n", snapshot.Name, snapshot.UUID) + err = client.VolumeSnapshots.Delete(context.Background(), snapshot.UUID) + if err != nil { + log.Fatalf("VolumeSnapshots.Delete returned error %s\n", err) + } + } + } + + return foundResource +} + func DeleteRemainingVolumes() bool { foundResource := false diff --git a/test/integration/tags_integration_test.go b/test/integration/tags_integration_test.go index 1233e8f..52717f6 100644 --- a/test/integration/tags_integration_test.go +++ b/test/integration/tags_integration_test.go @@ -109,7 +109,7 @@ func TestIntegrationTags_Server(t *testing.T) { func TestIntegrationTags_Volume(t *testing.T) { integrationTest(t) - createRequest := cloudscale.VolumeRequest{ + createRequest := cloudscale.VolumeCreateRequest{ Name: testRunPrefix, SizeGB: 3, } @@ -129,7 +129,7 @@ func TestIntegrationTags_Volume(t *testing.T) { t.Errorf("Tagging failed, could not tag, is at %s\n", getResult.Tags) } - updateRequest := cloudscale.VolumeRequest{} + updateRequest := cloudscale.VolumeUpdateRequest{} newTags := getNewTags() updateRequest.Tags = &newTags @@ -174,6 +174,197 @@ func TestIntegrationTags_Volume(t *testing.T) { } } +func TestIntegrationTags_VolumeFromSnapshot(t *testing.T) { + integrationTest(t) + ctx := context.Background() + + // first create a volume and a snapshot + createVolumeRequest := &cloudscale.VolumeCreateRequest{ + Name: testRunPrefix, + SizeGB: 3, + } + sourceVolume, err := client.Volumes.Create(ctx, createVolumeRequest) + if err != nil { + t.Fatalf("Volumes.Create returned error %s\n", err) + } + snapshotCreateRequest := &cloudscale.VolumeSnapshotCreateRequest{ + Name: testRunPrefix, + SourceVolume: sourceVolume.UUID, + } + snapshot, err := client.VolumeSnapshots.Create(ctx, snapshotCreateRequest) + if err != nil { + t.Fatalf("VolumeSnapshots.Create: %v", err) + } + + createVolumeFromSnapshotRequest := &cloudscale.VolumeCreateRequest{ + Name: fmt.Sprintf("%s-from-snapshot", testRunPrefix), + VolumeSnapshotUUID: snapshot.UUID, + } + + initialTags := getInitialTags() + createVolumeFromSnapshotRequest.Tags = &initialTags + + volume, err := client.Volumes.Create(ctx, createVolumeFromSnapshotRequest) + if err != nil { + t.Fatalf("Volumes.Create returned error %s\n", err) + } + + getResult, err := client.Volumes.Get(ctx, volume.UUID) + if err != nil { + t.Errorf("Volumes.Get returned error %s\n", err) + } + if !reflect.DeepEqual(getResult.Tags, initialTags) { + t.Errorf("Tagging failed, could not tag, is at %s\n", getResult.Tags) + } + + updateRequest := cloudscale.VolumeUpdateRequest{} + newTags := getNewTags() + updateRequest.Tags = &newTags + + err = client.Volumes.Update(ctx, volume.UUID, &updateRequest) + if err != nil { + t.Errorf("Volumes.Update returned error: %v", err) + } + getResult2, err := client.Volumes.Get(ctx, volume.UUID) + if err != nil { + t.Errorf("Volumes.Get returned error %s\n", err) + } + if !reflect.DeepEqual(getResult2.Tags, newTags) { + t.Errorf("Tagging failed, could not tag, is at %s\n", getResult.Tags) + } + + // test querying with tags + initialTagsKeyOnly := getInitialTagsKeyOnly() + for _, tags := range []cloudscale.TagMap{initialTags, initialTagsKeyOnly} { + res, err := client.Volumes.List(ctx, cloudscale.WithTagFilter(tags)) + if err != nil { + t.Errorf("Volumes.List returned error %s\n", err) + } + if len(res) > 0 { + t.Errorf("Expected no result when filter with %#v, got: %#v", tags, res) + } + } + + newTagsKeyOnly := getNewTagsKeyOnly() + for _, tags := range []cloudscale.TagMap{newTags, newTagsKeyOnly} { + res, err := client.Volumes.List(ctx, cloudscale.WithTagFilter(tags)) + if err != nil { + t.Errorf("Volumes.List returned error %s\n", err) + } + if len(res) != 1 { + t.Errorf("Expected exactly one result when filter with %#v, got: %#v", tags, len(res)) + } + } + + if err := client.VolumeSnapshots.Delete(ctx, snapshot.UUID); err != nil { + t.Fatalf("Warning: failed to delete snapshot %s: %v", snapshot.UUID, err) + } + + // Wait for snapshot to be fully deleted before deleting volume + // As a volume has been created, deletion can take a few seconds longer + err = waitForSnapshotDeletion(ctx, snapshot.UUID, 30) + if err != nil { + t.Fatalf("Snapshot deletion timeout: %v", err) + } + + if err := client.Volumes.Delete(ctx, sourceVolume.UUID); err != nil { + t.Fatalf("Volumes.Delete returned error %s: %v", sourceVolume.UUID, err) + } + + err = client.Volumes.Delete(ctx, volume.UUID) + if err != nil { + t.Fatalf("Volumes.Delete returned error %s\n", err) + } +} + +func TestIntegrationTags_Snapshot(t *testing.T) { + integrationTest(t) + + createVolumeRequest := cloudscale.VolumeCreateRequest{ + Name: testRunPrefix, + SizeGB: 3, + } + + volume, err := client.Volumes.Create(context.Background(), &createVolumeRequest) + if err != nil { + t.Fatalf("Volumes.Create returned error %s\n", err) + } + + snapshotCreateRequest := &cloudscale.VolumeSnapshotCreateRequest{ + Name: testRunPrefix, + SourceVolume: volume.UUID, + } + initialTags := getInitialTags() + snapshotCreateRequest.Tags = &initialTags + + snapshot, err := client.VolumeSnapshots.Create(context.Background(), snapshotCreateRequest) + if err != nil { + t.Fatalf("VolumeSnapshots.Create: %v", err) + } + + getResult, err := client.VolumeSnapshots.Get(context.Background(), snapshot.UUID) + if err != nil { + t.Errorf("VolumeSnapshots.Get returned error %s\n", err) + } + if !reflect.DeepEqual(getResult.Tags, initialTags) { + t.Errorf("Tagging failed, could not tag, is at %s\n", getResult.Tags) + } + + updateRequest := cloudscale.VolumeSnapshotUpdateRequest{} + newTags := getNewTags() + updateRequest.Tags = &newTags + + err = client.VolumeSnapshots.Update(context.Background(), snapshot.UUID, &updateRequest) + if err != nil { + t.Errorf("VolumeSnapshots.Update returned error: %v", err) + } + getResult2, err := client.VolumeSnapshots.Get(context.Background(), snapshot.UUID) + if err != nil { + t.Errorf("VolumeSnapshots.Get returned error %s\n", err) + } + if !reflect.DeepEqual(getResult2.Tags, newTags) { + t.Errorf("Tagging failed, could not tag, is at %s\n", getResult.Tags) + } + + // test querying with tags + initialTagsKeyOnly := getInitialTagsKeyOnly() + for _, tags := range []cloudscale.TagMap{initialTags, initialTagsKeyOnly} { + res, err := client.VolumeSnapshots.List(context.Background(), cloudscale.WithTagFilter(tags)) + if err != nil { + t.Errorf("VolumeSnapshots.List returned error %s\n", err) + } + if len(res) > 0 { + t.Errorf("Expected no result when filter with %#v, got: %#v", tags, res) + } + } + + newTagsKeyOnly := getNewTagsKeyOnly() + for _, tags := range []cloudscale.TagMap{newTags, newTagsKeyOnly} { + res, err := client.VolumeSnapshots.List(context.Background(), cloudscale.WithTagFilter(tags)) + if err != nil { + t.Errorf("VolumeSnapshots.List returned error %s\n", err) + } + if len(res) != 1 { + t.Errorf("Expected exactly one result when filter with %#v, got: %#v", tags, len(res)) + } + } + + err = client.VolumeSnapshots.Delete(context.Background(), snapshot.UUID) + if err != nil { + t.Fatalf("VolumeSnapshots.Delete returned error %s\n", err) + } + + // Wait for snapshot to be fully deleted before deleting volume + err = waitForSnapshotDeletion(context.Background(), snapshot.UUID, 10) + if err != nil { + t.Fatalf("Snapshot deletion timeout: %v", err) + } + + if err := client.Volumes.Delete(context.Background(), volume.UUID); err != nil { + t.Fatalf("Warning: failed to delete volume %s: %v", volume.UUID, err) + } +} + func TestIntegrationTags_FloatingIP(t *testing.T) { integrationTest(t) diff --git a/test/integration/volume_snapshots_integration_test.go b/test/integration/volume_snapshots_integration_test.go new file mode 100644 index 0000000..cb7ea3e --- /dev/null +++ b/test/integration/volume_snapshots_integration_test.go @@ -0,0 +1,171 @@ +//go:build integration + +package integration + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/cloudscale-ch/cloudscale-go-sdk/v6" +) + +func TestIntegrationVolumeSnapshot_CRUD(t *testing.T) { + integrationTest(t) + + ctx := context.Background() + + // A source volume is needed to create a snapshot. + volumeCreateRequest := &cloudscale.VolumeCreateRequest{ + Name: testRunPrefix, + SizeGB: 50, + Type: "ssd", + ZonalResourceRequest: cloudscale.ZonalResourceRequest{ + Zone: testZone, + }, + } + volume, err := client.Volumes.Create(ctx, volumeCreateRequest) + if err != nil { + t.Fatalf("Volume.Create: %v", err) + } + + volumeName := fmt.Sprintf("%s-snapshot", testRunPrefix) + snapshotCreateRequest := &cloudscale.VolumeSnapshotCreateRequest{ + Name: volumeName, + SourceVolume: volume.UUID, + } + snapshot, err := client.VolumeSnapshots.Create(ctx, snapshotCreateRequest) + if err != nil { + t.Fatalf("VolumeSnapshots.Create: %v", err) + } + + retrieved, err := client.VolumeSnapshots.Get(ctx, snapshot.UUID) + if err != nil { + t.Fatalf("VolumeSnapshots.Get: %v", err) + } + if retrieved.UUID != snapshot.UUID { + t.Errorf("Expected UUID %s, got %s", snapshot.UUID, retrieved.UUID) + } + if retrieved.Name != volumeName { + t.Errorf("Expected snapshot name '%s', got '%s'", volumeName, retrieved.Name) + } + if retrieved.SourceVolume.UUID != volume.UUID { + t.Errorf("Expected retrieved snapshot SourceVolume.UUID to be %s, got %q", volume.UUID, retrieved.SourceVolume.UUID) + } + + snapshots, err := client.VolumeSnapshots.List(ctx) + if err != nil { + t.Fatalf("VolumeSnapshots.List: %v", err) + } + if len(snapshots) == 0 { + t.Error("Expected at least one snapshot") + } + + if err := client.VolumeSnapshots.Delete(ctx, snapshot.UUID); err != nil { + t.Fatalf("Warning: failed to delete snapshot %s: %v", snapshot.UUID, err) + } + + // Wait for snapshot to be fully deleted before deleting volume + err = waitForSnapshotDeletion(ctx, snapshot.UUID, 10) + if err != nil { + t.Fatalf("Snapshot deletion timeout: %v", err) + } + + if err := client.Volumes.Delete(ctx, volume.UUID); err != nil { + t.Fatalf("Warning: failed to delete volume %s: %v", volume.UUID, err) + } +} + +func TestIntegrationVolumeSnapshot_Update(t *testing.T) { + integrationTest(t) + + ctx := context.Background() + + // A source volume is needed to create a snapshot. + volumeCreateRequest := &cloudscale.VolumeCreateRequest{ + Name: testRunPrefix, + SizeGB: 50, + Type: "ssd", + ZonalResourceRequest: cloudscale.ZonalResourceRequest{ + Zone: testZone, + }, + } + volume, err := client.Volumes.Create(ctx, volumeCreateRequest) + if err != nil { + t.Fatalf("Volume.Create: %v", err) + } + + snapshotCreateRequest := &cloudscale.VolumeSnapshotCreateRequest{ + Name: testRunPrefix, + SourceVolume: volume.UUID, + } + snapshot, err := client.VolumeSnapshots.Create(ctx, snapshotCreateRequest) + if err != nil { + t.Fatalf("VolumeSnapshots.Create: %v", err) + } + + updatedName := fmt.Sprintf("%s-updated", testRunPrefix) + + snapshotUpdateRequest := &cloudscale.VolumeSnapshotUpdateRequest{ + Name: updatedName, + } + err = client.VolumeSnapshots.Update(ctx, snapshot.UUID, snapshotUpdateRequest) + if err != nil { + t.Fatalf("VolumeSnapshots.Update: %v", err) + } + + // Get snapshot again to verify the update + updatedSnapshot, err := client.VolumeSnapshots.Get(ctx, snapshot.UUID) + if err != nil { + t.Fatalf("VolumeSnapshots.Get after update: %v", err) + } + if updatedSnapshot.Name != updatedName { + t.Errorf("Expected updated snapshot name '%s', got '%s'", updatedName, updatedSnapshot.Name) + } + + if err := client.VolumeSnapshots.Delete(ctx, snapshot.UUID); err != nil { + t.Fatalf("Warning: failed to delete snapshot %s: %v", snapshot.UUID, err) + } + + // Wait for snapshot to be fully deleted before deleting volume + err = waitForSnapshotDeletion(ctx, snapshot.UUID, 10) + if err != nil { + t.Fatalf("Snapshot deletion timeout: %v", err) + } + + if err := client.Volumes.Delete(ctx, volume.UUID); err != nil { + t.Fatalf("Warning: failed to delete volume %s: %v", volume.UUID, err) + } +} + +// waitForSnapshotDeletion polls the API until the snapshot no longer exists +func waitForSnapshotDeletion(ctx context.Context, snapshotUUID string, maxWaitSeconds int) error { + for i := 0; i < maxWaitSeconds; i++ { + snapshot, err := client.VolumeSnapshots.Get(ctx, snapshotUUID) + if err != nil { + + if apiErr, ok := err.(*cloudscale.ErrorResponse); ok { + if apiErr.StatusCode == 404 { + // if we get a 404 error, snapshot is gone, deletion completed + return nil + } + } + // some other error occurred + return err + } + + // if snapshot still exists, it must be in state deleting + if snapshot.Status != "deleting" { + return fmt.Errorf( + "snapshot %s exists but is in unexpected state %q while waiting for deletion", + snapshotUUID, + snapshot.Status, + ) + } + + // snapshot still exists, wait 1 second and try again + time.Sleep(1 * time.Second) + } + return fmt.Errorf("snapshot %s still exists after %d seconds", snapshotUUID, maxWaitSeconds) +} diff --git a/test/integration/volumes_integration_test.go b/test/integration/volumes_integration_test.go index dbb3a5e..a7989d0 100644 --- a/test/integration/volumes_integration_test.go +++ b/test/integration/volumes_integration_test.go @@ -5,6 +5,7 @@ package integration import ( "context" + "fmt" "strings" "sync" "testing" @@ -40,7 +41,7 @@ func TestIntegrationVolume_CreateAttached(t *testing.T) { t.Fatalf("Servers.WaitFor returned error %s\n", err) } - createVolumeRequest := &cloudscale.VolumeRequest{ + createVolumeRequest := &cloudscale.VolumeCreateRequest{ Name: testRunPrefix, SizeGB: 50, ServerUUIDs: &[]string{server.UUID}, @@ -56,14 +57,14 @@ func TestIntegrationVolume_CreateAttached(t *testing.T) { } time.Sleep(3 * time.Second) - detachVolumeRequest := &cloudscale.VolumeRequest{ + detachVolumeRequest := &cloudscale.VolumeUpdateRequest{ ServerUUIDs: &[]string{}, } err = client.Volumes.Update(context.TODO(), volume.UUID, detachVolumeRequest) if err != nil { t.Errorf("Volumes.Update returned error %s\n", err) } - attachVolumeRequest := &cloudscale.VolumeRequest{ + attachVolumeRequest := &cloudscale.VolumeUpdateRequest{ ServerUUIDs: &[]string{server.UUID}, } @@ -83,8 +84,61 @@ func TestIntegrationVolume_CreateAttached(t *testing.T) { } } +func TestIntegrationVolume_CreateFromSnapshot(t *testing.T) { + integrationTest(t) + ctx := context.Background() + + // volume is need to create a snapshot + createVolumeRequest := &cloudscale.VolumeCreateRequest{ + Name: fmt.Sprintf("%s-source", testRunPrefix), + SizeGB: 10, + } + volume, err := client.Volumes.Create(ctx, createVolumeRequest) + if err != nil { + t.Fatalf("Volumes.Create returned error %s\n", err) + } + + snapshotCreateRequest := &cloudscale.VolumeSnapshotCreateRequest{ + Name: testRunPrefix, + SourceVolume: volume.UUID, + } + snapshot, err := client.VolumeSnapshots.Create(ctx, snapshotCreateRequest) + if err != nil { + t.Fatalf("VolumeSnapshots.Create: %v", err) + } + + createVolumeFromSnapshotRequest := &cloudscale.VolumeCreateRequest{ + Name: fmt.Sprintf("%s-from-snapshot", testRunPrefix), + VolumeSnapshotUUID: snapshot.UUID, + } + + volumeCreatedFromSnapshot, err := client.Volumes.Create(ctx, createVolumeFromSnapshotRequest) + if err != nil { + t.Fatalf("Volumes.Create: %v", err) + } + + if err := client.VolumeSnapshots.Delete(ctx, snapshot.UUID); err != nil { + t.Fatalf("Warning: failed to delete snapshot %s: %v", snapshot.UUID, err) + } + + // Wait for snapshot to be fully deleted before deleting volume + // As a volume has been created, deletion can take a few seconds longer + err = waitForSnapshotDeletion(ctx, snapshot.UUID, 30) + if err != nil { + t.Fatalf("Snapshot deletion timeout: %v", err) + } + + if err := client.Volumes.Delete(ctx, volume.UUID); err != nil { + t.Fatalf("Warning: failed to delete volume %s: %v", volume.UUID, err) + } + + if err := client.Volumes.Delete(ctx, volumeCreatedFromSnapshot.UUID); err != nil { + t.Fatalf("Warning: failed to delete volume %s: %v", volume.UUID, err) + } +} + func TestIntegrationVolume_CreateWithoutServer(t *testing.T) { - createVolumeRequest := &cloudscale.VolumeRequest{ + createVolumeRequest := &cloudscale.VolumeCreateRequest{ Name: testRunPrefix, SizeGB: 50, } @@ -109,7 +163,7 @@ func TestIntegrationVolume_CreateWithoutServer(t *testing.T) { t.Errorf("Volume %s not found\n", volume.UUID) } - multiUpdateVolumeRequest := &cloudscale.VolumeRequest{ + multiUpdateVolumeRequest := &cloudscale.VolumeUpdateRequest{ SizeGB: 50, Name: testRunPrefix + "Foo", } @@ -133,7 +187,7 @@ func TestIntegrationVolume_CreateWithoutServer(t *testing.T) { const scaleSize = 200 // Try to scale. - scaleVolumeRequest := &cloudscale.VolumeRequest{SizeGB: scaleSize} + scaleVolumeRequest := &cloudscale.VolumeUpdateRequest{SizeGB: scaleSize} err = client.Volumes.Update(context.TODO(), volume.UUID, scaleVolumeRequest) getVolume, err := client.Volumes.Get(context.TODO(), volume.UUID) if err == nil { @@ -151,7 +205,7 @@ func TestIntegrationVolume_CreateWithoutServer(t *testing.T) { } func TestIntegrationVolume_AttachToNewServer(t *testing.T) { - createVolumeRequest := &cloudscale.VolumeRequest{ + createVolumeRequest := &cloudscale.VolumeCreateRequest{ Name: testRunPrefix, SizeGB: 50, } @@ -184,7 +238,7 @@ func TestIntegrationVolume_AttachToNewServer(t *testing.T) { if err != nil { t.Fatalf("Servers.WaitFor returned error %s\n", err) } - volumeAttachRequest := &cloudscale.VolumeRequest{ + volumeAttachRequest := &cloudscale.VolumeUpdateRequest{ ServerUUIDs: &[]string{server.UUID}, } @@ -205,7 +259,7 @@ func TestIntegrationVolume_AttachToNewServer(t *testing.T) { func TestIntegrationVolume_ListByName(t *testing.T) { volumeName := testRunPrefix + "-name-test" - createVolumeRequest := &cloudscale.VolumeRequest{ + createVolumeRequest := &cloudscale.VolumeCreateRequest{ Name: volumeName, SizeGB: 5, } @@ -270,7 +324,7 @@ func TestIntegrationVolume_MultiSite(t *testing.T) { func createVolumeInZoneAndAssert(t *testing.T, zone cloudscale.Zone, wg *sync.WaitGroup) { defer wg.Done() - createVolumeRequest := &cloudscale.VolumeRequest{ + createVolumeRequest := &cloudscale.VolumeCreateRequest{ Name: testRunPrefix, SizeGB: 50, } diff --git a/volume_snapshots.go b/volume_snapshots.go new file mode 100644 index 0000000..b654588 --- /dev/null +++ b/volume_snapshots.go @@ -0,0 +1,35 @@ +package cloudscale + +const volumeSnapshotsBasePath = "v1/volume-snapshots" + +type VolumeSnapshot struct { + ZonalResource + TaggedResource + HREF string `json:"href,omitempty"` + UUID string `json:"uuid,omitempty"` + Name string `json:"name,omitempty"` + SizeGB int `json:"size_gb,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + SourceVolume VolumeStub `json:"source_volume,omitempty"` + Status string `json:"status,omitempty"` +} + +type VolumeSnapshotCreateRequest struct { + TaggedResourceRequest + Name string `json:"name,omitempty"` + SourceVolume string `json:"source_volume,omitempty"` +} + +type VolumeSnapshotUpdateRequest struct { + TaggedResourceRequest + Name string `json:"name,omitempty"` +} + +type VolumeSnapshotService interface { + GenericCreateService[VolumeSnapshot, VolumeSnapshotCreateRequest] + GenericGetService[VolumeSnapshot] + GenericListService[VolumeSnapshot] + GenericUpdateService[VolumeSnapshot, VolumeSnapshotUpdateRequest] + GenericDeleteService[VolumeSnapshot] + GenericWaitForService[VolumeSnapshot] +} diff --git a/volumes.go b/volumes.go index ad76fa9..2413c0f 100644 --- a/volumes.go +++ b/volumes.go @@ -22,7 +22,17 @@ type Volume struct { CreatedAt time.Time `json:"created_at"` } -type VolumeRequest struct { +type VolumeCreateRequest struct { + ZonalResourceRequest + TaggedResourceRequest + Name string `json:"name,omitempty"` + SizeGB int `json:"size_gb,omitempty"` + Type string `json:"type,omitempty"` + ServerUUIDs *[]string `json:"server_uuids,omitempty"` + VolumeSnapshotUUID string `json:"volume_snapshot_uuid,omitempty"` +} + +type VolumeUpdateRequest struct { ZonalResourceRequest TaggedResourceRequest Name string `json:"name,omitempty"` @@ -32,10 +42,10 @@ type VolumeRequest struct { } type VolumeService interface { - GenericCreateService[Volume, VolumeRequest] + GenericCreateService[Volume, VolumeCreateRequest] GenericGetService[Volume] GenericListService[Volume] - GenericUpdateService[Volume, VolumeRequest] + GenericUpdateService[Volume, VolumeUpdateRequest] GenericDeleteService[Volume] GenericWaitForService[Volume] }