diff --git a/CHANGELOG.md b/CHANGELOG.md index b7b0a4759..3fb1cf63e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All changes to `doctl` will be documented in this file. ## [1.17.0] - UNRELEASED - #438 Remove need to opt-in to database commands. - @andrewsomething + - #420 Allow creating Volumes from a Snapshots. - @bentranter ## [1.16.0] - 2019-04-25 diff --git a/args.go b/args.go index 7d0d38119..3d00f212e 100644 --- a/args.go +++ b/args.go @@ -166,6 +166,8 @@ const ( ArgVolumeDesc = "desc" // ArgVolumeRegion is the region of a volume. ArgVolumeRegion = "region" + // ArgVolumeSnapshot is the snapshot from which to create a volume. + ArgVolumeSnapshot = "snapshot" // ArgVolumeFilesystemType is the filesystem type for a volume. ArgVolumeFilesystemType = "fs-type" // ArgVolumeFilesystemLabel is the filesystem label for a volume. diff --git a/commands/displayers/volume.go b/commands/displayers/volume.go index 796bcc0ea..aeaa8bd49 100644 --- a/commands/displayers/volume.go +++ b/commands/displayers/volume.go @@ -60,11 +60,14 @@ func (a *Volume) KV() []map[string]interface{} { "ID": volume.ID, "Name": volume.Name, "Size": strconv.FormatInt(volume.SizeGigaBytes, 10) + " GiB", - "Region": volume.Region.Slug, "Filesystem Type": volume.FilesystemType, "Filesystem Label": volume.FilesystemLabel, "Tags": strings.Join(volume.Tags, ","), } + m["Region"] = "" + if volume.Region != nil { + m["Region"] = volume.Region.Slug + } m["DropletIDs"] = "" if len(volume.DropletIDs) != 0 { m["DropletIDs"] = fmt.Sprintf("%v", volume.DropletIDs) diff --git a/commands/volumes.go b/commands/volumes.go index ce934cbf1..57a912c6d 100644 --- a/commands/volumes.go +++ b/commands/volumes.go @@ -44,8 +44,8 @@ func Volume() *Command { AddStringFlag(cmdVolumeCreate, doctl.ArgVolumeSize, "", "4TiB", "Volume size", requiredOpt()) AddStringFlag(cmdVolumeCreate, doctl.ArgVolumeDesc, "", "", "Volume description") - AddStringFlag(cmdVolumeCreate, doctl.ArgVolumeRegion, "", "", "Volume region", - requiredOpt()) + AddStringFlag(cmdVolumeCreate, doctl.ArgVolumeRegion, "", "", "Volume region; should not be specified with a snapshot") + AddStringFlag(cmdVolumeCreate, doctl.ArgVolumeSnapshot, "", "", "Volume snapshot; should not specified with a region") AddStringFlag(cmdVolumeCreate, doctl.ArgVolumeFilesystemType, "", "", "Volume filesystem type (ext4 or xfs)") AddStringFlag(cmdVolumeCreate, doctl.ArgVolumeFilesystemLabel, "", "", "Volume filesystem label") AddStringSliceFlag(cmdVolumeCreate, doctl.ArgTag, "", []string{}, "tags to apply to the volume; comma separate or repeat --tag to add multiple tags at once") @@ -147,7 +147,16 @@ func RunVolumeCreate(c *CmdConfig) error { region, err := c.Doit.GetString(c.NS, doctl.ArgVolumeRegion) if err != nil { return err + } + + snapshotID, err := c.Doit.GetString(c.NS, doctl.ArgVolumeSnapshot) + if err != nil { + return err + } + if region == "" && snapshotID == "" { + errorMsg := fmt.Sprintf("%s.%s || %s.%s", c.NS, doctl.ArgVolumeRegion, c.NS, doctl.ArgVolumeSnapshot) + return doctl.NewMissingArgsErr(errorMsg) } fsType, err := c.Doit.GetString(c.NS, doctl.ArgVolumeFilesystemType) @@ -170,6 +179,7 @@ func RunVolumeCreate(c *CmdConfig) error { createVolume.SizeGigaBytes = int64(size / (1 << 30)) createVolume.Description = desc createVolume.Region = region + createVolume.SnapshotID = snapshotID createVolume.FilesystemType = fsType createVolume.FilesystemLabel = fsLabel createVolume.Tags = tags diff --git a/commands/volumes_test.go b/commands/volumes_test.go index 84492d0b6..bfe861228 100644 --- a/commands/volumes_test.go +++ b/commands/volumes_test.go @@ -109,6 +109,29 @@ func TestVolumeCreate(t *testing.T) { }) } +func TestVolumeCreateFromSnapshot(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + tcr := godo.VolumeCreateRequest{ + Name: "test-volume", + SizeGigaBytes: 100, + SnapshotID: "ed6414f7-7873-4dd2-90cf-f4f354c293e6", + Description: "test description", + Tags: []string{"one", "two"}, + } + tm.volumes.On("CreateVolume", &tcr).Return(&testVolume, nil) + + config.Args = append(config.Args, "test-volume") + + config.Doit.Set(config.NS, doctl.ArgVolumeSnapshot, "ed6414f7-7873-4dd2-90cf-f4f354c293e6") + config.Doit.Set(config.NS, doctl.ArgVolumeSize, "100GiB") + config.Doit.Set(config.NS, doctl.ArgVolumeDesc, "test description") + config.Doit.Set(config.NS, doctl.ArgTag, []string{"one", "two"}) + + err := RunVolumeCreate(config) + assert.NoError(t, err) + }) +} + func TestVolumesDelete(t *testing.T) { withTestClient(t, func(config *CmdConfig, tm *tcMocks) { tm.volumes.On("DeleteVolume", "test-volume").Return(nil)