From e093a56d84f4a3a808f51faa6fc74161285f0197 Mon Sep 17 00:00:00 2001 From: Nishad Mathur Date: Wed, 17 Jun 2026 14:15:55 -0700 Subject: [PATCH 1/3] Add dynamic disk CLI commands (TNZ-99509, TNZ-109499) Implements five new CLI commands and extends bosh disks: Phase 1 (TNZ-99509 - provide/detach/delete): bosh provide-disk DISK-NAME INSTANCE-GROUP/ID --disk-pool POOL --size MB bosh detach-dynamic-disk DISK-NAME bosh delete-dynamic-disk DISK-NAME Phase 2 (TNZ-109499 - create/attach standalone + list): bosh create-dynamic-disk DISK-NAME --disk-pool POOL --size MB bosh attach-dynamic-disk DISK-NAME INSTANCE-GROUP/ID bosh disks --dynamic Director interface extended with ProvideDynamicDisk, DetachDynamicDisk, DeleteDynamicDisk, DynamicDisks, CreateDynamicDisk, AttachDynamicDisk. DynamicDisk interface added with counterfeiter fake generated. 947 cmd package tests, 0 failures. [TNZ-99509] [TNZ-109499] Co-authored-by: Cursor --- cmd/attach_dynamic_disk.go | 23 + cmd/attach_dynamic_disk_test.go | 62 +++ cmd/cmd.go | 15 + cmd/create_dynamic_disk.go | 28 ++ cmd/create_dynamic_disk_test.go | 64 +++ cmd/delete_dynamic_disk.go | 25 + cmd/delete_dynamic_disk_test.go | 64 +++ cmd/detach_dynamic_disk.go | 25 + cmd/detach_dynamic_disk_test.go | 64 +++ cmd/disks.go | 43 +- cmd/disks_test.go | 63 ++- cmd/opts/opts.go | 65 +++ cmd/provide_disk.go | 29 ++ cmd/provide_disk_test.go | 71 +++ director/directorfakes/fake_director.go | 454 +++++++++++++++++++ director/directorfakes/fake_dynamic_disk.go | 478 ++++++++++++++++++++ director/dynamic_disks.go | 187 ++++++++ director/interfaces.go | 20 + 18 files changed, 1778 insertions(+), 2 deletions(-) create mode 100644 cmd/attach_dynamic_disk.go create mode 100644 cmd/attach_dynamic_disk_test.go create mode 100644 cmd/create_dynamic_disk.go create mode 100644 cmd/create_dynamic_disk_test.go create mode 100644 cmd/delete_dynamic_disk.go create mode 100644 cmd/delete_dynamic_disk_test.go create mode 100644 cmd/detach_dynamic_disk.go create mode 100644 cmd/detach_dynamic_disk_test.go create mode 100644 cmd/provide_disk.go create mode 100644 cmd/provide_disk_test.go create mode 100644 director/directorfakes/fake_dynamic_disk.go create mode 100644 director/dynamic_disks.go diff --git a/cmd/attach_dynamic_disk.go b/cmd/attach_dynamic_disk.go new file mode 100644 index 000000000..206c66b6f --- /dev/null +++ b/cmd/attach_dynamic_disk.go @@ -0,0 +1,23 @@ +package cmd + +import ( + . "github.com/cloudfoundry/bosh-cli/v7/cmd/opts" //nolint:staticcheck + boshdir "github.com/cloudfoundry/bosh-cli/v7/director" + boshui "github.com/cloudfoundry/bosh-cli/v7/ui" +) + +type AttachDynamicDiskCmd struct { + ui boshui.UI + director boshdir.Director +} + +func NewAttachDynamicDiskCmd(ui boshui.UI, director boshdir.Director) AttachDynamicDiskCmd { + return AttachDynamicDiskCmd{ui: ui, director: director} +} + +func (c AttachDynamicDiskCmd) Run(opts AttachDynamicDiskOpts) error { + instanceID := opts.Args.InstanceID.String() + diskName := opts.Args.DiskName + + return c.director.AttachDynamicDisk(diskName, instanceID) +} diff --git a/cmd/attach_dynamic_disk_test.go b/cmd/attach_dynamic_disk_test.go new file mode 100644 index 000000000..860017825 --- /dev/null +++ b/cmd/attach_dynamic_disk_test.go @@ -0,0 +1,62 @@ +package cmd_test + +import ( + "errors" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/cloudfoundry/bosh-cli/v7/cmd" + "github.com/cloudfoundry/bosh-cli/v7/cmd/opts" + boshdir "github.com/cloudfoundry/bosh-cli/v7/director" + fakedir "github.com/cloudfoundry/bosh-cli/v7/director/directorfakes" + fakeui "github.com/cloudfoundry/bosh-cli/v7/ui/fakes" +) + +var _ = Describe("AttachDynamicDiskCmd", func() { + var ( + ui *fakeui.FakeUI + director *fakedir.FakeDirector + command cmd.AttachDynamicDiskCmd + ) + + BeforeEach(func() { + ui = &fakeui.FakeUI{} + director = &fakedir.FakeDirector{} + command = cmd.NewAttachDynamicDiskCmd(ui, director) + }) + + Describe("Run", func() { + var attachOpts opts.AttachDynamicDiskOpts + + BeforeEach(func() { + slug := boshdir.NewInstanceSlug("worker", "xyz789") + + attachOpts = opts.AttachDynamicDiskOpts{ + Args: opts.AttachDynamicDiskArgs{ + DiskName: "my-disk", + InstanceID: slug, + }, + } + }) + + act := func() error { return command.Run(attachOpts) } + + It("attaches the dynamic disk to the instance", func() { + err := act() + Expect(err).ToNot(HaveOccurred()) + + diskName, instanceID := director.AttachDynamicDiskArgsForCall(0) + Expect(diskName).To(Equal("my-disk")) + Expect(instanceID).To(Equal("worker/xyz789")) + }) + + It("returns error if attaching fails", func() { + director.AttachDynamicDiskReturns(errors.New("fake-err")) + + err := act() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("fake-err")) + }) + }) +}) diff --git a/cmd/cmd.go b/cmd/cmd.go index 4514a0801..55385ad32 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -252,6 +252,21 @@ func (c Cmd) Execute() (cmdErr error) { case *OrphanDiskOpts: return NewOrphanDiskCmd(deps.UI, c.director()).Run(*opts) + case *ProvideDiskOpts: + return NewProvideDiskCmd(deps.UI, c.director()).Run(*opts) + + case *DetachDynamicDiskOpts: + return NewDetachDynamicDiskCmd(deps.UI, c.director()).Run(*opts) + + case *DeleteDynamicDiskOpts: + return NewDeleteDynamicDiskCmd(deps.UI, c.director()).Run(*opts) + + case *CreateDynamicDiskOpts: + return NewCreateDynamicDiskCmd(deps.UI, c.director()).Run(*opts) + + case *AttachDynamicDiskOpts: + return NewAttachDynamicDiskCmd(deps.UI, c.director()).Run(*opts) + case *NetworksOpts: return NewNetworksCmd(deps.UI, c.director()).Run(*opts) diff --git a/cmd/create_dynamic_disk.go b/cmd/create_dynamic_disk.go new file mode 100644 index 000000000..ce880d11b --- /dev/null +++ b/cmd/create_dynamic_disk.go @@ -0,0 +1,28 @@ +package cmd + +import ( + . "github.com/cloudfoundry/bosh-cli/v7/cmd/opts" //nolint:staticcheck + boshdir "github.com/cloudfoundry/bosh-cli/v7/director" + boshui "github.com/cloudfoundry/bosh-cli/v7/ui" +) + +type CreateDynamicDiskCmd struct { + ui boshui.UI + director boshdir.Director +} + +func NewCreateDynamicDiskCmd(ui boshui.UI, director boshdir.Director) CreateDynamicDiskCmd { + return CreateDynamicDiskCmd{ui: ui, director: director} +} + +func (c CreateDynamicDiskCmd) Run(opts CreateDynamicDiskOpts) error { + diskName := opts.Args.DiskName + + diskCID, err := c.director.CreateDynamicDisk(diskName, opts.DiskPool, opts.Size, nil) + if err != nil { + return err + } + + c.ui.PrintLinef("Dynamic disk '%s' created (CID: %s)", diskName, diskCID) + return nil +} diff --git a/cmd/create_dynamic_disk_test.go b/cmd/create_dynamic_disk_test.go new file mode 100644 index 000000000..30fd6e60d --- /dev/null +++ b/cmd/create_dynamic_disk_test.go @@ -0,0 +1,64 @@ +package cmd_test + +import ( + "errors" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/cloudfoundry/bosh-cli/v7/cmd" + "github.com/cloudfoundry/bosh-cli/v7/cmd/opts" + fakedir "github.com/cloudfoundry/bosh-cli/v7/director/directorfakes" + fakeui "github.com/cloudfoundry/bosh-cli/v7/ui/fakes" +) + +var _ = Describe("CreateDynamicDiskCmd", func() { + var ( + ui *fakeui.FakeUI + director *fakedir.FakeDirector + command cmd.CreateDynamicDiskCmd + ) + + BeforeEach(func() { + ui = &fakeui.FakeUI{} + director = &fakedir.FakeDirector{} + command = cmd.NewCreateDynamicDiskCmd(ui, director) + }) + + Describe("Run", func() { + var createOpts opts.CreateDynamicDiskOpts + + BeforeEach(func() { + createOpts = opts.CreateDynamicDiskOpts{ + Args: opts.CreateDynamicDiskArgs{DiskName: "my-disk"}, + DiskPool: "large", + Size: 102400, + } + }) + + act := func() error { return command.Run(createOpts) } + + It("creates a dynamic disk without attaching", func() { + director.CreateDynamicDiskReturns("disk-cid-456", nil) + + err := act() + Expect(err).ToNot(HaveOccurred()) + + diskName, diskPool, sizeInMB, metadata := director.CreateDynamicDiskArgsForCall(0) + Expect(diskName).To(Equal("my-disk")) + Expect(diskPool).To(Equal("large")) + Expect(sizeInMB).To(Equal(102400)) + Expect(metadata).To(BeNil()) + + Expect(ui.Said).To(ContainElement(ContainSubstring("my-disk"))) + }) + + It("returns error if creation fails", func() { + director.CreateDynamicDiskReturns("", errors.New("fake-err")) + + err := act() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("fake-err")) + }) + }) +}) diff --git a/cmd/delete_dynamic_disk.go b/cmd/delete_dynamic_disk.go new file mode 100644 index 000000000..cc7aaf5e2 --- /dev/null +++ b/cmd/delete_dynamic_disk.go @@ -0,0 +1,25 @@ +package cmd + +import ( + . "github.com/cloudfoundry/bosh-cli/v7/cmd/opts" //nolint:staticcheck + boshdir "github.com/cloudfoundry/bosh-cli/v7/director" + boshui "github.com/cloudfoundry/bosh-cli/v7/ui" +) + +type DeleteDynamicDiskCmd struct { + ui boshui.UI + director boshdir.Director +} + +func NewDeleteDynamicDiskCmd(ui boshui.UI, director boshdir.Director) DeleteDynamicDiskCmd { + return DeleteDynamicDiskCmd{ui: ui, director: director} +} + +func (c DeleteDynamicDiskCmd) Run(opts DeleteDynamicDiskOpts) error { + err := c.ui.AskForConfirmation() + if err != nil { + return err + } + + return c.director.DeleteDynamicDisk(opts.Args.DiskName) +} diff --git a/cmd/delete_dynamic_disk_test.go b/cmd/delete_dynamic_disk_test.go new file mode 100644 index 000000000..f43df4b13 --- /dev/null +++ b/cmd/delete_dynamic_disk_test.go @@ -0,0 +1,64 @@ +package cmd_test + +import ( + "errors" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/cloudfoundry/bosh-cli/v7/cmd" + "github.com/cloudfoundry/bosh-cli/v7/cmd/opts" + fakedir "github.com/cloudfoundry/bosh-cli/v7/director/directorfakes" + fakeui "github.com/cloudfoundry/bosh-cli/v7/ui/fakes" +) + +var _ = Describe("DeleteDynamicDiskCmd", func() { + var ( + ui *fakeui.FakeUI + director *fakedir.FakeDirector + command cmd.DeleteDynamicDiskCmd + ) + + BeforeEach(func() { + ui = &fakeui.FakeUI{} + director = &fakedir.FakeDirector{} + command = cmd.NewDeleteDynamicDiskCmd(ui, director) + }) + + Describe("Run", func() { + var deleteOpts opts.DeleteDynamicDiskOpts + + BeforeEach(func() { + deleteOpts = opts.DeleteDynamicDiskOpts{ + Args: opts.DeleteDynamicDiskArgs{DiskName: "my-disk"}, + } + }) + + act := func() error { return command.Run(deleteOpts) } + + It("deletes the dynamic disk", func() { + err := act() + Expect(err).ToNot(HaveOccurred()) + + Expect(director.DeleteDynamicDiskArgsForCall(0)).To(Equal("my-disk")) + }) + + It("returns error if deletion fails", func() { + director.DeleteDynamicDiskReturns(errors.New("fake-err")) + + err := act() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("fake-err")) + }) + + It("does not delete if confirmation is rejected", func() { + ui.AskedConfirmationErr = errors.New("stop") + + err := act() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("stop")) + + Expect(director.DeleteDynamicDiskCallCount()).To(Equal(0)) + }) + }) +}) diff --git a/cmd/detach_dynamic_disk.go b/cmd/detach_dynamic_disk.go new file mode 100644 index 000000000..ea64f36b0 --- /dev/null +++ b/cmd/detach_dynamic_disk.go @@ -0,0 +1,25 @@ +package cmd + +import ( + . "github.com/cloudfoundry/bosh-cli/v7/cmd/opts" //nolint:staticcheck + boshdir "github.com/cloudfoundry/bosh-cli/v7/director" + boshui "github.com/cloudfoundry/bosh-cli/v7/ui" +) + +type DetachDynamicDiskCmd struct { + ui boshui.UI + director boshdir.Director +} + +func NewDetachDynamicDiskCmd(ui boshui.UI, director boshdir.Director) DetachDynamicDiskCmd { + return DetachDynamicDiskCmd{ui: ui, director: director} +} + +func (c DetachDynamicDiskCmd) Run(opts DetachDynamicDiskOpts) error { + err := c.ui.AskForConfirmation() + if err != nil { + return err + } + + return c.director.DetachDynamicDisk(opts.Args.DiskName) +} diff --git a/cmd/detach_dynamic_disk_test.go b/cmd/detach_dynamic_disk_test.go new file mode 100644 index 000000000..10d8163ee --- /dev/null +++ b/cmd/detach_dynamic_disk_test.go @@ -0,0 +1,64 @@ +package cmd_test + +import ( + "errors" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/cloudfoundry/bosh-cli/v7/cmd" + "github.com/cloudfoundry/bosh-cli/v7/cmd/opts" + fakedir "github.com/cloudfoundry/bosh-cli/v7/director/directorfakes" + fakeui "github.com/cloudfoundry/bosh-cli/v7/ui/fakes" +) + +var _ = Describe("DetachDynamicDiskCmd", func() { + var ( + ui *fakeui.FakeUI + director *fakedir.FakeDirector + command cmd.DetachDynamicDiskCmd + ) + + BeforeEach(func() { + ui = &fakeui.FakeUI{} + director = &fakedir.FakeDirector{} + command = cmd.NewDetachDynamicDiskCmd(ui, director) + }) + + Describe("Run", func() { + var detachOpts opts.DetachDynamicDiskOpts + + BeforeEach(func() { + detachOpts = opts.DetachDynamicDiskOpts{ + Args: opts.DetachDynamicDiskArgs{DiskName: "my-disk"}, + } + }) + + act := func() error { return command.Run(detachOpts) } + + It("detaches the dynamic disk", func() { + err := act() + Expect(err).ToNot(HaveOccurred()) + + Expect(director.DetachDynamicDiskArgsForCall(0)).To(Equal("my-disk")) + }) + + It("returns error if detaching fails", func() { + director.DetachDynamicDiskReturns(errors.New("fake-err")) + + err := act() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("fake-err")) + }) + + It("does not detach if confirmation is rejected", func() { + ui.AskedConfirmationErr = errors.New("stop") + + err := act() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("stop")) + + Expect(director.DetachDynamicDiskCallCount()).To(Equal(0)) + }) + }) +}) diff --git a/cmd/disks.go b/cmd/disks.go index b79909179..26ac7888c 100644 --- a/cmd/disks.go +++ b/cmd/disks.go @@ -19,8 +19,12 @@ func NewDisksCmd(ui boshui.UI, director boshdir.Director) DisksCmd { } func (c DisksCmd) Run(opts DisksOpts) error { + if opts.Dynamic { + return c.runDynamic() + } + if !opts.Orphaned { - return errors.New("Only --orphaned is supported") //nolint:staticcheck + return errors.New("Only --orphaned or --dynamic is supported") //nolint:staticcheck } disks, err := c.director.OrphanDisks() @@ -56,3 +60,40 @@ func (c DisksCmd) Run(opts DisksOpts) error { return nil } + +func (c DisksCmd) runDynamic() error { + disks, err := c.director.DynamicDisks() + if err != nil { + return err + } + + table := boshtbl.Table{ + Content: "dynamic disks", + Header: []boshtbl.Header{ + boshtbl.NewHeader("Name"), + boshtbl.NewHeader("Disk CID"), + boshtbl.NewHeader("Size"), + boshtbl.NewHeader("Deployment"), + boshtbl.NewHeader("Instance"), + boshtbl.NewHeader("AZ"), + boshtbl.NewHeader("CPI"), + }, + SortBy: []boshtbl.ColumnSort{{Column: 0}}, + } + + for _, d := range disks { + table.Rows = append(table.Rows, []boshtbl.Value{ + boshtbl.NewValueString(d.Name()), + boshtbl.NewValueString(d.DiskCID()), + boshtbl.NewValueMegaBytes(d.Size()), + boshtbl.NewValueString(d.DeploymentName()), + boshtbl.NewValueString(d.InstanceName()), + boshtbl.NewValueString(d.AvailabilityZone()), + boshtbl.NewValueString(d.CPI()), + }) + } + + c.ui.PrintTable(table) + + return nil +} diff --git a/cmd/disks_test.go b/cmd/disks_test.go index 4cb4b67bb..0696948d6 100644 --- a/cmd/disks_test.go +++ b/cmd/disks_test.go @@ -106,7 +106,68 @@ var _ = Describe("DisksCmd", func() { }) It("returns error if orphaned disks were not requested", func() { - Expect(act()).To(Equal(errors.New("Only --orphaned is supported"))) + Expect(act()).To(Equal(errors.New("Only --orphaned or --dynamic is supported"))) + }) + + Context("when dynamic disks requested", func() { + BeforeEach(func() { + disksOpts.Dynamic = true + }) + + It("lists dynamic disks", func() { + disks := []boshdir.DynamicDisk{ + &fakedir.FakeDynamicDisk{ + NameStub: func() string { return "my-disk" }, + DiskCIDStub: func() string { return "disk-cid-1" }, + SizeStub: func() uint64 { return 2048 }, + DeploymentNameStub: func() string { return "my-deployment" }, + InstanceNameStub: func() string { return "api/abc123" }, + AvailabilityZoneStub: func() string { return "z1" }, + CPIStub: func() string { return "aws_cpi" }, + }, + } + + director.DynamicDisksReturns(disks, nil) + + err := act() + Expect(err).ToNot(HaveOccurred()) + + Expect(ui.Table).To(Equal(boshtbl.Table{ + Content: "dynamic disks", + + Header: []boshtbl.Header{ + boshtbl.NewHeader("Name"), + boshtbl.NewHeader("Disk CID"), + boshtbl.NewHeader("Size"), + boshtbl.NewHeader("Deployment"), + boshtbl.NewHeader("Instance"), + boshtbl.NewHeader("AZ"), + boshtbl.NewHeader("CPI"), + }, + + SortBy: []boshtbl.ColumnSort{{Column: 0}}, + + Rows: [][]boshtbl.Value{ + { + boshtbl.NewValueString("my-disk"), + boshtbl.NewValueString("disk-cid-1"), + boshtbl.NewValueMegaBytes(2048), + boshtbl.NewValueString("my-deployment"), + boshtbl.NewValueString("api/abc123"), + boshtbl.NewValueString("z1"), + boshtbl.NewValueString("aws_cpi"), + }, + }, + })) + }) + + It("returns error if dynamic disks cannot be retrieved", func() { + director.DynamicDisksReturns(nil, errors.New("fake-err")) + + err := act() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("fake-err")) + }) }) }) }) diff --git a/cmd/opts/opts.go b/cmd/opts/opts.go index 4be6b19f9..3f5ded480 100644 --- a/cmd/opts/opts.go +++ b/cmd/opts/opts.go @@ -123,6 +123,13 @@ type BoshOpts struct { DeleteDisk DeleteDiskOpts `command:"delete-disk" description:"Delete disk"` OrphanDisk OrphanDiskOpts `command:"orphan-disk" description:"Orphan disk"` + // Dynamic Disks + ProvideDisk ProvideDiskOpts `command:"provide-disk" description:"Provide (create+attach) a dynamic disk to a VM"` + DetachDynamicDisk DetachDynamicDiskOpts `command:"detach-dynamic-disk" description:"Detach a dynamic disk from its VM"` + DeleteDynamicDisk DeleteDynamicDiskOpts `command:"delete-dynamic-disk" description:"Delete a dynamic disk"` + CreateDynamicDisk CreateDynamicDiskOpts `command:"create-dynamic-disk" description:"Create a dynamic disk without attaching it"` + AttachDynamicDisk AttachDynamicDiskOpts `command:"attach-dynamic-disk" description:"Attach an existing dynamic disk to a VM"` + // Networks Networks NetworksOpts `command:"networks" description:"List networks"` DeleteNetwork DeleteNetworkOpts `command:"delete-network" description:"Delete network"` @@ -745,6 +752,7 @@ type DeleteNetworkArgs struct { type DisksOpts struct { Orphaned bool `long:"orphaned" short:"o" description:"List orphaned disks"` + Dynamic bool `long:"dynamic" short:"d" description:"List dynamic disks"` cmd } @@ -765,6 +773,63 @@ type OrphanDiskArgs struct { CID string `positional-arg-name:"CID"` } +// Dynamic Disks + +type ProvideDiskOpts struct { + Args ProvideDiskArgs `positional-args:"true" required:"true"` + + DiskPool string `long:"disk-pool" short:"p" description:"Disk pool / type name to use when creating the disk" required:"true"` + Size int `long:"size" short:"s" description:"Disk size in MB" required:"true"` + + cmd +} + +type ProvideDiskArgs struct { + DiskName string `positional-arg-name:"DISK-NAME"` + InstanceID boshdir.InstanceSlug `positional-arg-name:"INSTANCE-GROUP/INSTANCE-ID"` +} + +type DetachDynamicDiskOpts struct { + Args DetachDynamicDiskArgs `positional-args:"true" required:"true"` + cmd +} + +type DetachDynamicDiskArgs struct { + DiskName string `positional-arg-name:"DISK-NAME"` +} + +type DeleteDynamicDiskOpts struct { + Args DeleteDynamicDiskArgs `positional-args:"true" required:"true"` + cmd +} + +type DeleteDynamicDiskArgs struct { + DiskName string `positional-arg-name:"DISK-NAME"` +} + +type CreateDynamicDiskOpts struct { + Args CreateDynamicDiskArgs `positional-args:"true" required:"true"` + + DiskPool string `long:"disk-pool" short:"p" description:"Disk pool / type name to use when creating the disk" required:"true"` + Size int `long:"size" short:"s" description:"Disk size in MB" required:"true"` + + cmd +} + +type CreateDynamicDiskArgs struct { + DiskName string `positional-arg-name:"DISK-NAME"` +} + +type AttachDynamicDiskOpts struct { + Args AttachDynamicDiskArgs `positional-args:"true" required:"true"` + cmd +} + +type AttachDynamicDiskArgs struct { + DiskName string `positional-arg-name:"DISK-NAME"` + InstanceID boshdir.InstanceSlug `positional-arg-name:"INSTANCE-GROUP/INSTANCE-ID"` +} + // Snapshots type SnapshotsOpts struct { diff --git a/cmd/provide_disk.go b/cmd/provide_disk.go new file mode 100644 index 000000000..7eca3455c --- /dev/null +++ b/cmd/provide_disk.go @@ -0,0 +1,29 @@ +package cmd + +import ( + . "github.com/cloudfoundry/bosh-cli/v7/cmd/opts" //nolint:staticcheck + boshdir "github.com/cloudfoundry/bosh-cli/v7/director" + boshui "github.com/cloudfoundry/bosh-cli/v7/ui" +) + +type ProvideDiskCmd struct { + ui boshui.UI + director boshdir.Director +} + +func NewProvideDiskCmd(ui boshui.UI, director boshdir.Director) ProvideDiskCmd { + return ProvideDiskCmd{ui: ui, director: director} +} + +func (c ProvideDiskCmd) Run(opts ProvideDiskOpts) error { + instanceID := opts.Args.InstanceID.String() + diskName := opts.Args.DiskName + + diskCID, err := c.director.ProvideDynamicDisk(instanceID, diskName, opts.DiskPool, opts.Size, nil) + if err != nil { + return err + } + + c.ui.PrintLinef("Disk '%s' provided (CID: %s)", diskName, diskCID) + return nil +} diff --git a/cmd/provide_disk_test.go b/cmd/provide_disk_test.go new file mode 100644 index 000000000..0184f8c2d --- /dev/null +++ b/cmd/provide_disk_test.go @@ -0,0 +1,71 @@ +package cmd_test + +import ( + "errors" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/cloudfoundry/bosh-cli/v7/cmd" + "github.com/cloudfoundry/bosh-cli/v7/cmd/opts" + boshdir "github.com/cloudfoundry/bosh-cli/v7/director" + fakedir "github.com/cloudfoundry/bosh-cli/v7/director/directorfakes" + fakeui "github.com/cloudfoundry/bosh-cli/v7/ui/fakes" +) + +var _ = Describe("ProvideDiskCmd", func() { + var ( + ui *fakeui.FakeUI + director *fakedir.FakeDirector + command cmd.ProvideDiskCmd + ) + + BeforeEach(func() { + ui = &fakeui.FakeUI{} + director = &fakedir.FakeDirector{} + command = cmd.NewProvideDiskCmd(ui, director) + }) + + Describe("Run", func() { + var provideDiskOpts opts.ProvideDiskOpts + + BeforeEach(func() { + slug := boshdir.NewInstanceSlug("web", "abc123") + + provideDiskOpts = opts.ProvideDiskOpts{ + Args: opts.ProvideDiskArgs{ + DiskName: "my-disk", + InstanceID: slug, + }, + DiskPool: "large", + Size: 51200, + } + }) + + act := func() error { return command.Run(provideDiskOpts) } + + It("provides (create+attach) a dynamic disk", func() { + director.ProvideDynamicDiskReturns("disk-cid-123", nil) + + err := act() + Expect(err).ToNot(HaveOccurred()) + + instanceID, diskName, diskPool, sizeInMB, metadata := director.ProvideDynamicDiskArgsForCall(0) + Expect(instanceID).To(Equal("web/abc123")) + Expect(diskName).To(Equal("my-disk")) + Expect(diskPool).To(Equal("large")) + Expect(sizeInMB).To(Equal(51200)) + Expect(metadata).To(BeNil()) + + Expect(ui.Said).To(ContainElement(ContainSubstring("my-disk"))) + }) + + It("returns error if providing disk failed", func() { + director.ProvideDynamicDiskReturns("", errors.New("fake-err")) + + err := act() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("fake-err")) + }) + }) +}) diff --git a/director/directorfakes/fake_director.go b/director/directorfakes/fake_director.go index 005417839..1d1099d6f 100644 --- a/director/directorfakes/fake_director.go +++ b/director/directorfakes/fake_director.go @@ -9,6 +9,18 @@ import ( ) type FakeDirector struct { + AttachDynamicDiskStub func(string, string) error + attachDynamicDiskMutex sync.RWMutex + attachDynamicDiskArgsForCall []struct { + arg1 string + arg2 string + } + attachDynamicDiskReturns struct { + result1 error + } + attachDynamicDiskReturnsOnCall map[int]struct { + result1 error + } CancelTasksStub func(director.TasksFilter) error cancelTasksMutex sync.RWMutex cancelTasksArgsForCall []struct { @@ -47,6 +59,22 @@ type FakeDirector struct { result1 director.CleanUp result2 error } + CreateDynamicDiskStub func(string, string, int, map[string]interface{}) (string, error) + createDynamicDiskMutex sync.RWMutex + createDynamicDiskArgsForCall []struct { + arg1 string + arg2 string + arg3 int + arg4 map[string]interface{} + } + createDynamicDiskReturns struct { + result1 string + result2 error + } + createDynamicDiskReturnsOnCall map[int]struct { + result1 string + result2 error + } CurrentTasksStub func(director.TasksFilter) ([]director.Task, error) currentTasksMutex sync.RWMutex currentTasksArgsForCall []struct { @@ -87,6 +115,17 @@ type FakeDirector struct { result1 bool result2 error } + DeleteDynamicDiskStub func(string) error + deleteDynamicDiskMutex sync.RWMutex + deleteDynamicDiskArgsForCall []struct { + arg1 string + } + deleteDynamicDiskReturns struct { + result1 error + } + deleteDynamicDiskReturnsOnCall map[int]struct { + result1 error + } DeploymentsStub func() ([]director.Deployment, error) deploymentsMutex sync.RWMutex deploymentsArgsForCall []struct { @@ -99,6 +138,17 @@ type FakeDirector struct { result1 []director.Deployment result2 error } + DetachDynamicDiskStub func(string) error + detachDynamicDiskMutex sync.RWMutex + detachDynamicDiskArgsForCall []struct { + arg1 string + } + detachDynamicDiskReturns struct { + result1 error + } + detachDynamicDiskReturnsOnCall map[int]struct { + result1 error + } DiffCPIConfigStub func([]byte, bool) (director.ConfigDiff, error) diffCPIConfigMutex sync.RWMutex diffCPIConfigArgsForCall []struct { @@ -185,6 +235,18 @@ type FakeDirector struct { downloadResourceUncheckedReturnsOnCall map[int]struct { result1 error } + DynamicDisksStub func() ([]director.DynamicDisk, error) + dynamicDisksMutex sync.RWMutex + dynamicDisksArgsForCall []struct { + } + dynamicDisksReturns struct { + result1 []director.DynamicDisk + result2 error + } + dynamicDisksReturnsOnCall map[int]struct { + result1 []director.DynamicDisk + result2 error + } EnableResurrectionStub func(bool) error enableResurrectionMutex sync.RWMutex enableResurrectionArgsForCall []struct { @@ -542,6 +604,23 @@ type FakeDirector struct { result1 []director.OrphanedVM result2 error } + ProvideDynamicDiskStub func(string, string, string, int, map[string]interface{}) (string, error) + provideDynamicDiskMutex sync.RWMutex + provideDynamicDiskArgsForCall []struct { + arg1 string + arg2 string + arg3 string + arg4 int + arg5 map[string]interface{} + } + provideDynamicDiskReturns struct { + result1 string + result2 error + } + provideDynamicDiskReturnsOnCall map[int]struct { + result1 string + result2 error + } RecentTasksStub func(int, director.TasksFilter) ([]director.Task, error) recentTasksMutex sync.RWMutex recentTasksArgsForCall []struct { @@ -725,6 +804,68 @@ type FakeDirector struct { invocationsMutex sync.RWMutex } +func (fake *FakeDirector) AttachDynamicDisk(arg1 string, arg2 string) error { + fake.attachDynamicDiskMutex.Lock() + ret, specificReturn := fake.attachDynamicDiskReturnsOnCall[len(fake.attachDynamicDiskArgsForCall)] + fake.attachDynamicDiskArgsForCall = append(fake.attachDynamicDiskArgsForCall, struct { + arg1 string + arg2 string + }{arg1, arg2}) + stub := fake.AttachDynamicDiskStub + fakeReturns := fake.attachDynamicDiskReturns + fake.recordInvocation("AttachDynamicDisk", []interface{}{arg1, arg2}) + fake.attachDynamicDiskMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeDirector) AttachDynamicDiskCallCount() int { + fake.attachDynamicDiskMutex.RLock() + defer fake.attachDynamicDiskMutex.RUnlock() + return len(fake.attachDynamicDiskArgsForCall) +} + +func (fake *FakeDirector) AttachDynamicDiskCalls(stub func(string, string) error) { + fake.attachDynamicDiskMutex.Lock() + defer fake.attachDynamicDiskMutex.Unlock() + fake.AttachDynamicDiskStub = stub +} + +func (fake *FakeDirector) AttachDynamicDiskArgsForCall(i int) (string, string) { + fake.attachDynamicDiskMutex.RLock() + defer fake.attachDynamicDiskMutex.RUnlock() + argsForCall := fake.attachDynamicDiskArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeDirector) AttachDynamicDiskReturns(result1 error) { + fake.attachDynamicDiskMutex.Lock() + defer fake.attachDynamicDiskMutex.Unlock() + fake.AttachDynamicDiskStub = nil + fake.attachDynamicDiskReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeDirector) AttachDynamicDiskReturnsOnCall(i int, result1 error) { + fake.attachDynamicDiskMutex.Lock() + defer fake.attachDynamicDiskMutex.Unlock() + fake.AttachDynamicDiskStub = nil + if fake.attachDynamicDiskReturnsOnCall == nil { + fake.attachDynamicDiskReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.attachDynamicDiskReturnsOnCall[i] = struct { + result1 error + }{result1} +} + func (fake *FakeDirector) CancelTasks(arg1 director.TasksFilter) error { fake.cancelTasksMutex.Lock() ret, specificReturn := fake.cancelTasksReturnsOnCall[len(fake.cancelTasksArgsForCall)] @@ -908,6 +1049,73 @@ func (fake *FakeDirector) CleanUpReturnsOnCall(i int, result1 director.CleanUp, }{result1, result2} } +func (fake *FakeDirector) CreateDynamicDisk(arg1 string, arg2 string, arg3 int, arg4 map[string]interface{}) (string, error) { + fake.createDynamicDiskMutex.Lock() + ret, specificReturn := fake.createDynamicDiskReturnsOnCall[len(fake.createDynamicDiskArgsForCall)] + fake.createDynamicDiskArgsForCall = append(fake.createDynamicDiskArgsForCall, struct { + arg1 string + arg2 string + arg3 int + arg4 map[string]interface{} + }{arg1, arg2, arg3, arg4}) + stub := fake.CreateDynamicDiskStub + fakeReturns := fake.createDynamicDiskReturns + fake.recordInvocation("CreateDynamicDisk", []interface{}{arg1, arg2, arg3, arg4}) + fake.createDynamicDiskMutex.Unlock() + if stub != nil { + return stub(arg1, arg2, arg3, arg4) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeDirector) CreateDynamicDiskCallCount() int { + fake.createDynamicDiskMutex.RLock() + defer fake.createDynamicDiskMutex.RUnlock() + return len(fake.createDynamicDiskArgsForCall) +} + +func (fake *FakeDirector) CreateDynamicDiskCalls(stub func(string, string, int, map[string]interface{}) (string, error)) { + fake.createDynamicDiskMutex.Lock() + defer fake.createDynamicDiskMutex.Unlock() + fake.CreateDynamicDiskStub = stub +} + +func (fake *FakeDirector) CreateDynamicDiskArgsForCall(i int) (string, string, int, map[string]interface{}) { + fake.createDynamicDiskMutex.RLock() + defer fake.createDynamicDiskMutex.RUnlock() + argsForCall := fake.createDynamicDiskArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 +} + +func (fake *FakeDirector) CreateDynamicDiskReturns(result1 string, result2 error) { + fake.createDynamicDiskMutex.Lock() + defer fake.createDynamicDiskMutex.Unlock() + fake.CreateDynamicDiskStub = nil + fake.createDynamicDiskReturns = struct { + result1 string + result2 error + }{result1, result2} +} + +func (fake *FakeDirector) CreateDynamicDiskReturnsOnCall(i int, result1 string, result2 error) { + fake.createDynamicDiskMutex.Lock() + defer fake.createDynamicDiskMutex.Unlock() + fake.CreateDynamicDiskStub = nil + if fake.createDynamicDiskReturnsOnCall == nil { + fake.createDynamicDiskReturnsOnCall = make(map[int]struct { + result1 string + result2 error + }) + } + fake.createDynamicDiskReturnsOnCall[i] = struct { + result1 string + result2 error + }{result1, result2} +} + func (fake *FakeDirector) CurrentTasks(arg1 director.TasksFilter) ([]director.Task, error) { fake.currentTasksMutex.Lock() ret, specificReturn := fake.currentTasksReturnsOnCall[len(fake.currentTasksArgsForCall)] @@ -1101,6 +1309,67 @@ func (fake *FakeDirector) DeleteConfigByIDReturnsOnCall(i int, result1 bool, res }{result1, result2} } +func (fake *FakeDirector) DeleteDynamicDisk(arg1 string) error { + fake.deleteDynamicDiskMutex.Lock() + ret, specificReturn := fake.deleteDynamicDiskReturnsOnCall[len(fake.deleteDynamicDiskArgsForCall)] + fake.deleteDynamicDiskArgsForCall = append(fake.deleteDynamicDiskArgsForCall, struct { + arg1 string + }{arg1}) + stub := fake.DeleteDynamicDiskStub + fakeReturns := fake.deleteDynamicDiskReturns + fake.recordInvocation("DeleteDynamicDisk", []interface{}{arg1}) + fake.deleteDynamicDiskMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeDirector) DeleteDynamicDiskCallCount() int { + fake.deleteDynamicDiskMutex.RLock() + defer fake.deleteDynamicDiskMutex.RUnlock() + return len(fake.deleteDynamicDiskArgsForCall) +} + +func (fake *FakeDirector) DeleteDynamicDiskCalls(stub func(string) error) { + fake.deleteDynamicDiskMutex.Lock() + defer fake.deleteDynamicDiskMutex.Unlock() + fake.DeleteDynamicDiskStub = stub +} + +func (fake *FakeDirector) DeleteDynamicDiskArgsForCall(i int) string { + fake.deleteDynamicDiskMutex.RLock() + defer fake.deleteDynamicDiskMutex.RUnlock() + argsForCall := fake.deleteDynamicDiskArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeDirector) DeleteDynamicDiskReturns(result1 error) { + fake.deleteDynamicDiskMutex.Lock() + defer fake.deleteDynamicDiskMutex.Unlock() + fake.DeleteDynamicDiskStub = nil + fake.deleteDynamicDiskReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeDirector) DeleteDynamicDiskReturnsOnCall(i int, result1 error) { + fake.deleteDynamicDiskMutex.Lock() + defer fake.deleteDynamicDiskMutex.Unlock() + fake.DeleteDynamicDiskStub = nil + if fake.deleteDynamicDiskReturnsOnCall == nil { + fake.deleteDynamicDiskReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.deleteDynamicDiskReturnsOnCall[i] = struct { + result1 error + }{result1} +} + func (fake *FakeDirector) Deployments() ([]director.Deployment, error) { fake.deploymentsMutex.Lock() ret, specificReturn := fake.deploymentsReturnsOnCall[len(fake.deploymentsArgsForCall)] @@ -1157,6 +1426,67 @@ func (fake *FakeDirector) DeploymentsReturnsOnCall(i int, result1 []director.Dep }{result1, result2} } +func (fake *FakeDirector) DetachDynamicDisk(arg1 string) error { + fake.detachDynamicDiskMutex.Lock() + ret, specificReturn := fake.detachDynamicDiskReturnsOnCall[len(fake.detachDynamicDiskArgsForCall)] + fake.detachDynamicDiskArgsForCall = append(fake.detachDynamicDiskArgsForCall, struct { + arg1 string + }{arg1}) + stub := fake.DetachDynamicDiskStub + fakeReturns := fake.detachDynamicDiskReturns + fake.recordInvocation("DetachDynamicDisk", []interface{}{arg1}) + fake.detachDynamicDiskMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeDirector) DetachDynamicDiskCallCount() int { + fake.detachDynamicDiskMutex.RLock() + defer fake.detachDynamicDiskMutex.RUnlock() + return len(fake.detachDynamicDiskArgsForCall) +} + +func (fake *FakeDirector) DetachDynamicDiskCalls(stub func(string) error) { + fake.detachDynamicDiskMutex.Lock() + defer fake.detachDynamicDiskMutex.Unlock() + fake.DetachDynamicDiskStub = stub +} + +func (fake *FakeDirector) DetachDynamicDiskArgsForCall(i int) string { + fake.detachDynamicDiskMutex.RLock() + defer fake.detachDynamicDiskMutex.RUnlock() + argsForCall := fake.detachDynamicDiskArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeDirector) DetachDynamicDiskReturns(result1 error) { + fake.detachDynamicDiskMutex.Lock() + defer fake.detachDynamicDiskMutex.Unlock() + fake.DetachDynamicDiskStub = nil + fake.detachDynamicDiskReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeDirector) DetachDynamicDiskReturnsOnCall(i int, result1 error) { + fake.detachDynamicDiskMutex.Lock() + defer fake.detachDynamicDiskMutex.Unlock() + fake.DetachDynamicDiskStub = nil + if fake.detachDynamicDiskReturnsOnCall == nil { + fake.detachDynamicDiskReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.detachDynamicDiskReturnsOnCall[i] = struct { + result1 error + }{result1} +} + func (fake *FakeDirector) DiffCPIConfig(arg1 []byte, arg2 bool) (director.ConfigDiff, error) { var arg1Copy []byte if arg1 != nil { @@ -1578,6 +1908,62 @@ func (fake *FakeDirector) DownloadResourceUncheckedReturnsOnCall(i int, result1 }{result1} } +func (fake *FakeDirector) DynamicDisks() ([]director.DynamicDisk, error) { + fake.dynamicDisksMutex.Lock() + ret, specificReturn := fake.dynamicDisksReturnsOnCall[len(fake.dynamicDisksArgsForCall)] + fake.dynamicDisksArgsForCall = append(fake.dynamicDisksArgsForCall, struct { + }{}) + stub := fake.DynamicDisksStub + fakeReturns := fake.dynamicDisksReturns + fake.recordInvocation("DynamicDisks", []interface{}{}) + fake.dynamicDisksMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeDirector) DynamicDisksCallCount() int { + fake.dynamicDisksMutex.RLock() + defer fake.dynamicDisksMutex.RUnlock() + return len(fake.dynamicDisksArgsForCall) +} + +func (fake *FakeDirector) DynamicDisksCalls(stub func() ([]director.DynamicDisk, error)) { + fake.dynamicDisksMutex.Lock() + defer fake.dynamicDisksMutex.Unlock() + fake.DynamicDisksStub = stub +} + +func (fake *FakeDirector) DynamicDisksReturns(result1 []director.DynamicDisk, result2 error) { + fake.dynamicDisksMutex.Lock() + defer fake.dynamicDisksMutex.Unlock() + fake.DynamicDisksStub = nil + fake.dynamicDisksReturns = struct { + result1 []director.DynamicDisk + result2 error + }{result1, result2} +} + +func (fake *FakeDirector) DynamicDisksReturnsOnCall(i int, result1 []director.DynamicDisk, result2 error) { + fake.dynamicDisksMutex.Lock() + defer fake.dynamicDisksMutex.Unlock() + fake.DynamicDisksStub = nil + if fake.dynamicDisksReturnsOnCall == nil { + fake.dynamicDisksReturnsOnCall = make(map[int]struct { + result1 []director.DynamicDisk + result2 error + }) + } + fake.dynamicDisksReturnsOnCall[i] = struct { + result1 []director.DynamicDisk + result2 error + }{result1, result2} +} + func (fake *FakeDirector) EnableResurrection(arg1 bool) error { fake.enableResurrectionMutex.Lock() ret, specificReturn := fake.enableResurrectionReturnsOnCall[len(fake.enableResurrectionArgsForCall)] @@ -3305,6 +3691,74 @@ func (fake *FakeDirector) OrphanedVMsReturnsOnCall(i int, result1 []director.Orp }{result1, result2} } +func (fake *FakeDirector) ProvideDynamicDisk(arg1 string, arg2 string, arg3 string, arg4 int, arg5 map[string]interface{}) (string, error) { + fake.provideDynamicDiskMutex.Lock() + ret, specificReturn := fake.provideDynamicDiskReturnsOnCall[len(fake.provideDynamicDiskArgsForCall)] + fake.provideDynamicDiskArgsForCall = append(fake.provideDynamicDiskArgsForCall, struct { + arg1 string + arg2 string + arg3 string + arg4 int + arg5 map[string]interface{} + }{arg1, arg2, arg3, arg4, arg5}) + stub := fake.ProvideDynamicDiskStub + fakeReturns := fake.provideDynamicDiskReturns + fake.recordInvocation("ProvideDynamicDisk", []interface{}{arg1, arg2, arg3, arg4, arg5}) + fake.provideDynamicDiskMutex.Unlock() + if stub != nil { + return stub(arg1, arg2, arg3, arg4, arg5) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeDirector) ProvideDynamicDiskCallCount() int { + fake.provideDynamicDiskMutex.RLock() + defer fake.provideDynamicDiskMutex.RUnlock() + return len(fake.provideDynamicDiskArgsForCall) +} + +func (fake *FakeDirector) ProvideDynamicDiskCalls(stub func(string, string, string, int, map[string]interface{}) (string, error)) { + fake.provideDynamicDiskMutex.Lock() + defer fake.provideDynamicDiskMutex.Unlock() + fake.ProvideDynamicDiskStub = stub +} + +func (fake *FakeDirector) ProvideDynamicDiskArgsForCall(i int) (string, string, string, int, map[string]interface{}) { + fake.provideDynamicDiskMutex.RLock() + defer fake.provideDynamicDiskMutex.RUnlock() + argsForCall := fake.provideDynamicDiskArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 +} + +func (fake *FakeDirector) ProvideDynamicDiskReturns(result1 string, result2 error) { + fake.provideDynamicDiskMutex.Lock() + defer fake.provideDynamicDiskMutex.Unlock() + fake.ProvideDynamicDiskStub = nil + fake.provideDynamicDiskReturns = struct { + result1 string + result2 error + }{result1, result2} +} + +func (fake *FakeDirector) ProvideDynamicDiskReturnsOnCall(i int, result1 string, result2 error) { + fake.provideDynamicDiskMutex.Lock() + defer fake.provideDynamicDiskMutex.Unlock() + fake.ProvideDynamicDiskStub = nil + if fake.provideDynamicDiskReturnsOnCall == nil { + fake.provideDynamicDiskReturnsOnCall = make(map[int]struct { + result1 string + result2 error + }) + } + fake.provideDynamicDiskReturnsOnCall[i] = struct { + result1 string + result2 error + }{result1, result2} +} + func (fake *FakeDirector) RecentTasks(arg1 int, arg2 director.TasksFilter) ([]director.Task, error) { fake.recentTasksMutex.Lock() ret, specificReturn := fake.recentTasksReturnsOnCall[len(fake.recentTasksArgsForCall)] diff --git a/director/directorfakes/fake_dynamic_disk.go b/director/directorfakes/fake_dynamic_disk.go new file mode 100644 index 000000000..4ea3ac2c9 --- /dev/null +++ b/director/directorfakes/fake_dynamic_disk.go @@ -0,0 +1,478 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package directorfakes + +import ( + "sync" + + "github.com/cloudfoundry/bosh-cli/v7/director" +) + +type FakeDynamicDisk struct { + AvailabilityZoneStub func() string + availabilityZoneMutex sync.RWMutex + availabilityZoneArgsForCall []struct { + } + availabilityZoneReturns struct { + result1 string + } + availabilityZoneReturnsOnCall map[int]struct { + result1 string + } + CPIStub func() string + cPIMutex sync.RWMutex + cPIArgsForCall []struct { + } + cPIReturns struct { + result1 string + } + cPIReturnsOnCall map[int]struct { + result1 string + } + DeploymentNameStub func() string + deploymentNameMutex sync.RWMutex + deploymentNameArgsForCall []struct { + } + deploymentNameReturns struct { + result1 string + } + deploymentNameReturnsOnCall map[int]struct { + result1 string + } + DiskCIDStub func() string + diskCIDMutex sync.RWMutex + diskCIDArgsForCall []struct { + } + diskCIDReturns struct { + result1 string + } + diskCIDReturnsOnCall map[int]struct { + result1 string + } + InstanceNameStub func() string + instanceNameMutex sync.RWMutex + instanceNameArgsForCall []struct { + } + instanceNameReturns struct { + result1 string + } + instanceNameReturnsOnCall map[int]struct { + result1 string + } + NameStub func() string + nameMutex sync.RWMutex + nameArgsForCall []struct { + } + nameReturns struct { + result1 string + } + nameReturnsOnCall map[int]struct { + result1 string + } + SizeStub func() uint64 + sizeMutex sync.RWMutex + sizeArgsForCall []struct { + } + sizeReturns struct { + result1 uint64 + } + sizeReturnsOnCall map[int]struct { + result1 uint64 + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *FakeDynamicDisk) AvailabilityZone() string { + fake.availabilityZoneMutex.Lock() + ret, specificReturn := fake.availabilityZoneReturnsOnCall[len(fake.availabilityZoneArgsForCall)] + fake.availabilityZoneArgsForCall = append(fake.availabilityZoneArgsForCall, struct { + }{}) + stub := fake.AvailabilityZoneStub + fakeReturns := fake.availabilityZoneReturns + fake.recordInvocation("AvailabilityZone", []interface{}{}) + fake.availabilityZoneMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeDynamicDisk) AvailabilityZoneCallCount() int { + fake.availabilityZoneMutex.RLock() + defer fake.availabilityZoneMutex.RUnlock() + return len(fake.availabilityZoneArgsForCall) +} + +func (fake *FakeDynamicDisk) AvailabilityZoneCalls(stub func() string) { + fake.availabilityZoneMutex.Lock() + defer fake.availabilityZoneMutex.Unlock() + fake.AvailabilityZoneStub = stub +} + +func (fake *FakeDynamicDisk) AvailabilityZoneReturns(result1 string) { + fake.availabilityZoneMutex.Lock() + defer fake.availabilityZoneMutex.Unlock() + fake.AvailabilityZoneStub = nil + fake.availabilityZoneReturns = struct { + result1 string + }{result1} +} + +func (fake *FakeDynamicDisk) AvailabilityZoneReturnsOnCall(i int, result1 string) { + fake.availabilityZoneMutex.Lock() + defer fake.availabilityZoneMutex.Unlock() + fake.AvailabilityZoneStub = nil + if fake.availabilityZoneReturnsOnCall == nil { + fake.availabilityZoneReturnsOnCall = make(map[int]struct { + result1 string + }) + } + fake.availabilityZoneReturnsOnCall[i] = struct { + result1 string + }{result1} +} + +func (fake *FakeDynamicDisk) CPI() string { + fake.cPIMutex.Lock() + ret, specificReturn := fake.cPIReturnsOnCall[len(fake.cPIArgsForCall)] + fake.cPIArgsForCall = append(fake.cPIArgsForCall, struct { + }{}) + stub := fake.CPIStub + fakeReturns := fake.cPIReturns + fake.recordInvocation("CPI", []interface{}{}) + fake.cPIMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeDynamicDisk) CPICallCount() int { + fake.cPIMutex.RLock() + defer fake.cPIMutex.RUnlock() + return len(fake.cPIArgsForCall) +} + +func (fake *FakeDynamicDisk) CPICalls(stub func() string) { + fake.cPIMutex.Lock() + defer fake.cPIMutex.Unlock() + fake.CPIStub = stub +} + +func (fake *FakeDynamicDisk) CPIReturns(result1 string) { + fake.cPIMutex.Lock() + defer fake.cPIMutex.Unlock() + fake.CPIStub = nil + fake.cPIReturns = struct { + result1 string + }{result1} +} + +func (fake *FakeDynamicDisk) CPIReturnsOnCall(i int, result1 string) { + fake.cPIMutex.Lock() + defer fake.cPIMutex.Unlock() + fake.CPIStub = nil + if fake.cPIReturnsOnCall == nil { + fake.cPIReturnsOnCall = make(map[int]struct { + result1 string + }) + } + fake.cPIReturnsOnCall[i] = struct { + result1 string + }{result1} +} + +func (fake *FakeDynamicDisk) DeploymentName() string { + fake.deploymentNameMutex.Lock() + ret, specificReturn := fake.deploymentNameReturnsOnCall[len(fake.deploymentNameArgsForCall)] + fake.deploymentNameArgsForCall = append(fake.deploymentNameArgsForCall, struct { + }{}) + stub := fake.DeploymentNameStub + fakeReturns := fake.deploymentNameReturns + fake.recordInvocation("DeploymentName", []interface{}{}) + fake.deploymentNameMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeDynamicDisk) DeploymentNameCallCount() int { + fake.deploymentNameMutex.RLock() + defer fake.deploymentNameMutex.RUnlock() + return len(fake.deploymentNameArgsForCall) +} + +func (fake *FakeDynamicDisk) DeploymentNameCalls(stub func() string) { + fake.deploymentNameMutex.Lock() + defer fake.deploymentNameMutex.Unlock() + fake.DeploymentNameStub = stub +} + +func (fake *FakeDynamicDisk) DeploymentNameReturns(result1 string) { + fake.deploymentNameMutex.Lock() + defer fake.deploymentNameMutex.Unlock() + fake.DeploymentNameStub = nil + fake.deploymentNameReturns = struct { + result1 string + }{result1} +} + +func (fake *FakeDynamicDisk) DeploymentNameReturnsOnCall(i int, result1 string) { + fake.deploymentNameMutex.Lock() + defer fake.deploymentNameMutex.Unlock() + fake.DeploymentNameStub = nil + if fake.deploymentNameReturnsOnCall == nil { + fake.deploymentNameReturnsOnCall = make(map[int]struct { + result1 string + }) + } + fake.deploymentNameReturnsOnCall[i] = struct { + result1 string + }{result1} +} + +func (fake *FakeDynamicDisk) DiskCID() string { + fake.diskCIDMutex.Lock() + ret, specificReturn := fake.diskCIDReturnsOnCall[len(fake.diskCIDArgsForCall)] + fake.diskCIDArgsForCall = append(fake.diskCIDArgsForCall, struct { + }{}) + stub := fake.DiskCIDStub + fakeReturns := fake.diskCIDReturns + fake.recordInvocation("DiskCID", []interface{}{}) + fake.diskCIDMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeDynamicDisk) DiskCIDCallCount() int { + fake.diskCIDMutex.RLock() + defer fake.diskCIDMutex.RUnlock() + return len(fake.diskCIDArgsForCall) +} + +func (fake *FakeDynamicDisk) DiskCIDCalls(stub func() string) { + fake.diskCIDMutex.Lock() + defer fake.diskCIDMutex.Unlock() + fake.DiskCIDStub = stub +} + +func (fake *FakeDynamicDisk) DiskCIDReturns(result1 string) { + fake.diskCIDMutex.Lock() + defer fake.diskCIDMutex.Unlock() + fake.DiskCIDStub = nil + fake.diskCIDReturns = struct { + result1 string + }{result1} +} + +func (fake *FakeDynamicDisk) DiskCIDReturnsOnCall(i int, result1 string) { + fake.diskCIDMutex.Lock() + defer fake.diskCIDMutex.Unlock() + fake.DiskCIDStub = nil + if fake.diskCIDReturnsOnCall == nil { + fake.diskCIDReturnsOnCall = make(map[int]struct { + result1 string + }) + } + fake.diskCIDReturnsOnCall[i] = struct { + result1 string + }{result1} +} + +func (fake *FakeDynamicDisk) InstanceName() string { + fake.instanceNameMutex.Lock() + ret, specificReturn := fake.instanceNameReturnsOnCall[len(fake.instanceNameArgsForCall)] + fake.instanceNameArgsForCall = append(fake.instanceNameArgsForCall, struct { + }{}) + stub := fake.InstanceNameStub + fakeReturns := fake.instanceNameReturns + fake.recordInvocation("InstanceName", []interface{}{}) + fake.instanceNameMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeDynamicDisk) InstanceNameCallCount() int { + fake.instanceNameMutex.RLock() + defer fake.instanceNameMutex.RUnlock() + return len(fake.instanceNameArgsForCall) +} + +func (fake *FakeDynamicDisk) InstanceNameCalls(stub func() string) { + fake.instanceNameMutex.Lock() + defer fake.instanceNameMutex.Unlock() + fake.InstanceNameStub = stub +} + +func (fake *FakeDynamicDisk) InstanceNameReturns(result1 string) { + fake.instanceNameMutex.Lock() + defer fake.instanceNameMutex.Unlock() + fake.InstanceNameStub = nil + fake.instanceNameReturns = struct { + result1 string + }{result1} +} + +func (fake *FakeDynamicDisk) InstanceNameReturnsOnCall(i int, result1 string) { + fake.instanceNameMutex.Lock() + defer fake.instanceNameMutex.Unlock() + fake.InstanceNameStub = nil + if fake.instanceNameReturnsOnCall == nil { + fake.instanceNameReturnsOnCall = make(map[int]struct { + result1 string + }) + } + fake.instanceNameReturnsOnCall[i] = struct { + result1 string + }{result1} +} + +func (fake *FakeDynamicDisk) Name() string { + fake.nameMutex.Lock() + ret, specificReturn := fake.nameReturnsOnCall[len(fake.nameArgsForCall)] + fake.nameArgsForCall = append(fake.nameArgsForCall, struct { + }{}) + stub := fake.NameStub + fakeReturns := fake.nameReturns + fake.recordInvocation("Name", []interface{}{}) + fake.nameMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeDynamicDisk) NameCallCount() int { + fake.nameMutex.RLock() + defer fake.nameMutex.RUnlock() + return len(fake.nameArgsForCall) +} + +func (fake *FakeDynamicDisk) NameCalls(stub func() string) { + fake.nameMutex.Lock() + defer fake.nameMutex.Unlock() + fake.NameStub = stub +} + +func (fake *FakeDynamicDisk) NameReturns(result1 string) { + fake.nameMutex.Lock() + defer fake.nameMutex.Unlock() + fake.NameStub = nil + fake.nameReturns = struct { + result1 string + }{result1} +} + +func (fake *FakeDynamicDisk) NameReturnsOnCall(i int, result1 string) { + fake.nameMutex.Lock() + defer fake.nameMutex.Unlock() + fake.NameStub = nil + if fake.nameReturnsOnCall == nil { + fake.nameReturnsOnCall = make(map[int]struct { + result1 string + }) + } + fake.nameReturnsOnCall[i] = struct { + result1 string + }{result1} +} + +func (fake *FakeDynamicDisk) Size() uint64 { + fake.sizeMutex.Lock() + ret, specificReturn := fake.sizeReturnsOnCall[len(fake.sizeArgsForCall)] + fake.sizeArgsForCall = append(fake.sizeArgsForCall, struct { + }{}) + stub := fake.SizeStub + fakeReturns := fake.sizeReturns + fake.recordInvocation("Size", []interface{}{}) + fake.sizeMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeDynamicDisk) SizeCallCount() int { + fake.sizeMutex.RLock() + defer fake.sizeMutex.RUnlock() + return len(fake.sizeArgsForCall) +} + +func (fake *FakeDynamicDisk) SizeCalls(stub func() uint64) { + fake.sizeMutex.Lock() + defer fake.sizeMutex.Unlock() + fake.SizeStub = stub +} + +func (fake *FakeDynamicDisk) SizeReturns(result1 uint64) { + fake.sizeMutex.Lock() + defer fake.sizeMutex.Unlock() + fake.SizeStub = nil + fake.sizeReturns = struct { + result1 uint64 + }{result1} +} + +func (fake *FakeDynamicDisk) SizeReturnsOnCall(i int, result1 uint64) { + fake.sizeMutex.Lock() + defer fake.sizeMutex.Unlock() + fake.SizeStub = nil + if fake.sizeReturnsOnCall == nil { + fake.sizeReturnsOnCall = make(map[int]struct { + result1 uint64 + }) + } + fake.sizeReturnsOnCall[i] = struct { + result1 uint64 + }{result1} +} + +func (fake *FakeDynamicDisk) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *FakeDynamicDisk) recordInvocation(key string, args []interface{}) { + fake.invocationsMutex.Lock() + defer fake.invocationsMutex.Unlock() + if fake.invocations == nil { + fake.invocations = map[string][][]interface{}{} + } + if fake.invocations[key] == nil { + fake.invocations[key] = [][]interface{}{} + } + fake.invocations[key] = append(fake.invocations[key], args) +} + +var _ director.DynamicDisk = new(FakeDynamicDisk) diff --git a/director/dynamic_disks.go b/director/dynamic_disks.go new file mode 100644 index 000000000..d60a25620 --- /dev/null +++ b/director/dynamic_disks.go @@ -0,0 +1,187 @@ +package director + +import ( + "encoding/json" + "fmt" + "net/url" + + bosherr "github.com/cloudfoundry/bosh-utils/errors" +) + +type DynamicDiskImpl struct { + client Client + + name string + diskCID string + deploymentName string + instanceName string + availabilityZone string + size uint64 + cpi string +} + +func (d DynamicDiskImpl) Name() string { return d.name } +func (d DynamicDiskImpl) DiskCID() string { return d.diskCID } +func (d DynamicDiskImpl) DeploymentName() string { return d.deploymentName } +func (d DynamicDiskImpl) InstanceName() string { return d.instanceName } +func (d DynamicDiskImpl) AvailabilityZone() string { return d.availabilityZone } +func (d DynamicDiskImpl) Size() uint64 { return d.size } +func (d DynamicDiskImpl) CPI() string { return d.cpi } + +type DynamicDiskResp struct { + Name string `json:"name"` + DiskCID string `json:"disk_cid"` + Deployment string `json:"deployment"` + Instance string `json:"instance"` + AvailabilityZone string `json:"availability_zone"` + Size uint64 `json:"size"` + CPI string `json:"cpi"` +} + +type ProvideDynamicDiskResult struct { + DiskCID string `json:"disk_cid"` +} + +// --- DirectorImpl delegation --- + +func (d DirectorImpl) ProvideDynamicDisk(instanceID, diskName, diskPool string, sizeInMB int, metadata map[string]interface{}) (string, error) { + return d.client.ProvideDynamicDisk(instanceID, diskName, diskPool, sizeInMB, metadata) +} + +func (d DirectorImpl) DetachDynamicDisk(diskName string) error { + return d.client.DetachDynamicDisk(diskName) +} + +func (d DirectorImpl) DeleteDynamicDisk(diskName string) error { + return d.client.DeleteDynamicDisk(diskName) +} + +func (d DirectorImpl) DynamicDisks() ([]DynamicDisk, error) { + return d.client.DynamicDisks() +} + +func (d DirectorImpl) CreateDynamicDisk(diskName, diskPool string, sizeInMB int, metadata map[string]interface{}) (string, error) { + return d.client.CreateDynamicDisk(diskName, diskPool, sizeInMB, metadata) +} + +func (d DirectorImpl) AttachDynamicDisk(diskName, instanceID string) error { + return d.client.AttachDynamicDisk(diskName, instanceID) +} + +// --- HTTP Client methods --- + +func (c Client) ProvideDynamicDisk(instanceID, diskName, diskPool string, sizeInMB int, metadata map[string]interface{}) (string, error) { + reqBody := map[string]interface{}{ + "instance_id": instanceID, + "disk_name": diskName, + "disk_pool_name": diskPool, + "disk_size": sizeInMB, + } + if metadata != nil { + reqBody["metadata"] = metadata + } + + bodyBytes, err := json.Marshal(reqBody) + if err != nil { + return "", bosherr.WrapError(err, "Marshaling provide dynamic disk request") + } + + resultBytes, err := c.taskClientRequest.PostResult("/dynamic_disks/provide", bodyBytes, nil) + if err != nil { + return "", bosherr.WrapErrorf(err, "Providing dynamic disk '%s'", diskName) + } + + var result ProvideDynamicDiskResult + if len(resultBytes) > 0 { + if parseErr := json.Unmarshal(resultBytes, &result); parseErr != nil { + return "", bosherr.WrapErrorf(parseErr, "Unmarshaling provide disk result") + } + } + return result.DiskCID, nil +} + +func (c Client) DetachDynamicDisk(diskName string) error { + path := fmt.Sprintf("/dynamic_disks/%s/detach", url.PathEscape(diskName)) + _, err := c.taskClientRequest.PostResult(path, nil, nil) + if err != nil { + return bosherr.WrapErrorf(err, "Detaching dynamic disk '%s'", diskName) + } + return nil +} + +func (c Client) DeleteDynamicDisk(diskName string) error { + path := fmt.Sprintf("/dynamic_disks/%s", url.PathEscape(diskName)) + _, err := c.taskClientRequest.DeleteResult(path) + if err != nil { + return bosherr.WrapErrorf(err, "Deleting dynamic disk '%s'", diskName) + } + return nil +} + +func (c Client) DynamicDisks() ([]DynamicDisk, error) { + var resps []DynamicDiskResp + if err := c.clientRequest.Get("/dynamic_disks", &resps); err != nil { + return nil, bosherr.WrapError(err, "Listing dynamic disks") + } + + var disks []DynamicDisk + for _, r := range resps { + disks = append(disks, DynamicDiskImpl{ + client: c, + name: r.Name, + diskCID: r.DiskCID, + deploymentName: r.Deployment, + instanceName: r.Instance, + availabilityZone: r.AvailabilityZone, + size: r.Size, + cpi: r.CPI, + }) + } + return disks, nil +} + +func (c Client) CreateDynamicDisk(diskName, diskPool string, sizeInMB int, metadata map[string]interface{}) (string, error) { + reqBody := map[string]interface{}{ + "disk_name": diskName, + "disk_pool_name": diskPool, + "disk_size": sizeInMB, + } + if metadata != nil { + reqBody["metadata"] = metadata + } + + bodyBytes, err := json.Marshal(reqBody) + if err != nil { + return "", bosherr.WrapError(err, "Marshaling create dynamic disk request") + } + + resultBytes, err := c.taskClientRequest.PostResult("/dynamic_disks", bodyBytes, nil) + if err != nil { + return "", bosherr.WrapErrorf(err, "Creating dynamic disk '%s'", diskName) + } + + var result ProvideDynamicDiskResult + if len(resultBytes) > 0 { + if parseErr := json.Unmarshal(resultBytes, &result); parseErr != nil { + return "", bosherr.WrapErrorf(parseErr, "Unmarshaling create disk result") + } + } + return result.DiskCID, nil +} + +func (c Client) AttachDynamicDisk(diskName, instanceID string) error { + reqBody := map[string]interface{}{ + "instance_id": instanceID, + } + bodyBytes, err := json.Marshal(reqBody) + if err != nil { + return bosherr.WrapError(err, "Marshaling attach dynamic disk request") + } + + path := fmt.Sprintf("/dynamic_disks/%s/attach", url.PathEscape(diskName)) + _, err = c.taskClientRequest.PostResult(path, bodyBytes, nil) + if err != nil { + return bosherr.WrapErrorf(err, "Attaching dynamic disk '%s' to instance '%s'", diskName, instanceID) + } + return nil +} diff --git a/director/interfaces.go b/director/interfaces.go index f68c0867b..3ee17c2bb 100644 --- a/director/interfaces.go +++ b/director/interfaces.go @@ -77,6 +77,14 @@ type Director interface { OrphanDisks() ([]OrphanDisk, error) OrphanDisk(string) error + // Dynamic disk operations (TNZ-99509, TNZ-109499) + ProvideDynamicDisk(instanceID, diskName, diskPool string, sizeInMB int, metadata map[string]interface{}) (string, error) + DetachDynamicDisk(diskName string) error + DeleteDynamicDisk(diskName string) error + DynamicDisks() ([]DynamicDisk, error) + CreateDynamicDisk(diskName, diskPool string, sizeInMB int, metadata map[string]interface{}) (string, error) + AttachDynamicDisk(diskName, instanceID string) error + FindOrphanNetwork(string) (OrphanNetwork, error) OrphanNetworks() ([]OrphanNetwork, error) @@ -322,6 +330,18 @@ type OrphanDisk interface { Delete() error } +//counterfeiter:generate . DynamicDisk + +type DynamicDisk interface { + Name() string + DiskCID() string + DeploymentName() string + InstanceName() string + AvailabilityZone() string + Size() uint64 + CPI() string +} + //counterfeiter:generate . OrphanNetwork type OrphanNetwork interface { From 98c57643d187d422d5000f6837860fd8b6ce44c2 Mon Sep 17 00:00:00 2001 From: Clay Kauzlaric Date: Thu, 25 Jun 2026 17:05:07 -0400 Subject: [PATCH 2/3] only implement list and delete for dynamic disks after talking with the team, we decided only these two should be available via the CLI other operations will still be available through the API ai-assisted=yes [TNZ-99509] [TNZ-109499] Signed-off-by: Nishad Mathur --- cmd/attach_dynamic_disk.go | 23 ----------- cmd/attach_dynamic_disk_test.go | 62 ---------------------------- cmd/cmd.go | 15 ------- cmd/create_dynamic_disk.go | 28 ------------- cmd/create_dynamic_disk_test.go | 64 ----------------------------- cmd/delete_disk.go | 4 ++ cmd/delete_disk_test.go | 34 ++++++++++++++++ cmd/delete_dynamic_disk.go | 25 ------------ cmd/delete_dynamic_disk_test.go | 64 ----------------------------- cmd/detach_dynamic_disk.go | 25 ------------ cmd/detach_dynamic_disk_test.go | 64 ----------------------------- cmd/opts/opts.go | 69 ++------------------------------ cmd/opts/opts_test.go | 8 ++++ cmd/provide_disk.go | 29 -------------- cmd/provide_disk_test.go | 71 --------------------------------- 15 files changed, 49 insertions(+), 536 deletions(-) delete mode 100644 cmd/attach_dynamic_disk.go delete mode 100644 cmd/attach_dynamic_disk_test.go delete mode 100644 cmd/create_dynamic_disk.go delete mode 100644 cmd/create_dynamic_disk_test.go delete mode 100644 cmd/delete_dynamic_disk.go delete mode 100644 cmd/delete_dynamic_disk_test.go delete mode 100644 cmd/detach_dynamic_disk.go delete mode 100644 cmd/detach_dynamic_disk_test.go delete mode 100644 cmd/provide_disk.go delete mode 100644 cmd/provide_disk_test.go diff --git a/cmd/attach_dynamic_disk.go b/cmd/attach_dynamic_disk.go deleted file mode 100644 index 206c66b6f..000000000 --- a/cmd/attach_dynamic_disk.go +++ /dev/null @@ -1,23 +0,0 @@ -package cmd - -import ( - . "github.com/cloudfoundry/bosh-cli/v7/cmd/opts" //nolint:staticcheck - boshdir "github.com/cloudfoundry/bosh-cli/v7/director" - boshui "github.com/cloudfoundry/bosh-cli/v7/ui" -) - -type AttachDynamicDiskCmd struct { - ui boshui.UI - director boshdir.Director -} - -func NewAttachDynamicDiskCmd(ui boshui.UI, director boshdir.Director) AttachDynamicDiskCmd { - return AttachDynamicDiskCmd{ui: ui, director: director} -} - -func (c AttachDynamicDiskCmd) Run(opts AttachDynamicDiskOpts) error { - instanceID := opts.Args.InstanceID.String() - diskName := opts.Args.DiskName - - return c.director.AttachDynamicDisk(diskName, instanceID) -} diff --git a/cmd/attach_dynamic_disk_test.go b/cmd/attach_dynamic_disk_test.go deleted file mode 100644 index 860017825..000000000 --- a/cmd/attach_dynamic_disk_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package cmd_test - -import ( - "errors" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/cloudfoundry/bosh-cli/v7/cmd" - "github.com/cloudfoundry/bosh-cli/v7/cmd/opts" - boshdir "github.com/cloudfoundry/bosh-cli/v7/director" - fakedir "github.com/cloudfoundry/bosh-cli/v7/director/directorfakes" - fakeui "github.com/cloudfoundry/bosh-cli/v7/ui/fakes" -) - -var _ = Describe("AttachDynamicDiskCmd", func() { - var ( - ui *fakeui.FakeUI - director *fakedir.FakeDirector - command cmd.AttachDynamicDiskCmd - ) - - BeforeEach(func() { - ui = &fakeui.FakeUI{} - director = &fakedir.FakeDirector{} - command = cmd.NewAttachDynamicDiskCmd(ui, director) - }) - - Describe("Run", func() { - var attachOpts opts.AttachDynamicDiskOpts - - BeforeEach(func() { - slug := boshdir.NewInstanceSlug("worker", "xyz789") - - attachOpts = opts.AttachDynamicDiskOpts{ - Args: opts.AttachDynamicDiskArgs{ - DiskName: "my-disk", - InstanceID: slug, - }, - } - }) - - act := func() error { return command.Run(attachOpts) } - - It("attaches the dynamic disk to the instance", func() { - err := act() - Expect(err).ToNot(HaveOccurred()) - - diskName, instanceID := director.AttachDynamicDiskArgsForCall(0) - Expect(diskName).To(Equal("my-disk")) - Expect(instanceID).To(Equal("worker/xyz789")) - }) - - It("returns error if attaching fails", func() { - director.AttachDynamicDiskReturns(errors.New("fake-err")) - - err := act() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("fake-err")) - }) - }) -}) diff --git a/cmd/cmd.go b/cmd/cmd.go index 55385ad32..4514a0801 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -252,21 +252,6 @@ func (c Cmd) Execute() (cmdErr error) { case *OrphanDiskOpts: return NewOrphanDiskCmd(deps.UI, c.director()).Run(*opts) - case *ProvideDiskOpts: - return NewProvideDiskCmd(deps.UI, c.director()).Run(*opts) - - case *DetachDynamicDiskOpts: - return NewDetachDynamicDiskCmd(deps.UI, c.director()).Run(*opts) - - case *DeleteDynamicDiskOpts: - return NewDeleteDynamicDiskCmd(deps.UI, c.director()).Run(*opts) - - case *CreateDynamicDiskOpts: - return NewCreateDynamicDiskCmd(deps.UI, c.director()).Run(*opts) - - case *AttachDynamicDiskOpts: - return NewAttachDynamicDiskCmd(deps.UI, c.director()).Run(*opts) - case *NetworksOpts: return NewNetworksCmd(deps.UI, c.director()).Run(*opts) diff --git a/cmd/create_dynamic_disk.go b/cmd/create_dynamic_disk.go deleted file mode 100644 index ce880d11b..000000000 --- a/cmd/create_dynamic_disk.go +++ /dev/null @@ -1,28 +0,0 @@ -package cmd - -import ( - . "github.com/cloudfoundry/bosh-cli/v7/cmd/opts" //nolint:staticcheck - boshdir "github.com/cloudfoundry/bosh-cli/v7/director" - boshui "github.com/cloudfoundry/bosh-cli/v7/ui" -) - -type CreateDynamicDiskCmd struct { - ui boshui.UI - director boshdir.Director -} - -func NewCreateDynamicDiskCmd(ui boshui.UI, director boshdir.Director) CreateDynamicDiskCmd { - return CreateDynamicDiskCmd{ui: ui, director: director} -} - -func (c CreateDynamicDiskCmd) Run(opts CreateDynamicDiskOpts) error { - diskName := opts.Args.DiskName - - diskCID, err := c.director.CreateDynamicDisk(diskName, opts.DiskPool, opts.Size, nil) - if err != nil { - return err - } - - c.ui.PrintLinef("Dynamic disk '%s' created (CID: %s)", diskName, diskCID) - return nil -} diff --git a/cmd/create_dynamic_disk_test.go b/cmd/create_dynamic_disk_test.go deleted file mode 100644 index 30fd6e60d..000000000 --- a/cmd/create_dynamic_disk_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package cmd_test - -import ( - "errors" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/cloudfoundry/bosh-cli/v7/cmd" - "github.com/cloudfoundry/bosh-cli/v7/cmd/opts" - fakedir "github.com/cloudfoundry/bosh-cli/v7/director/directorfakes" - fakeui "github.com/cloudfoundry/bosh-cli/v7/ui/fakes" -) - -var _ = Describe("CreateDynamicDiskCmd", func() { - var ( - ui *fakeui.FakeUI - director *fakedir.FakeDirector - command cmd.CreateDynamicDiskCmd - ) - - BeforeEach(func() { - ui = &fakeui.FakeUI{} - director = &fakedir.FakeDirector{} - command = cmd.NewCreateDynamicDiskCmd(ui, director) - }) - - Describe("Run", func() { - var createOpts opts.CreateDynamicDiskOpts - - BeforeEach(func() { - createOpts = opts.CreateDynamicDiskOpts{ - Args: opts.CreateDynamicDiskArgs{DiskName: "my-disk"}, - DiskPool: "large", - Size: 102400, - } - }) - - act := func() error { return command.Run(createOpts) } - - It("creates a dynamic disk without attaching", func() { - director.CreateDynamicDiskReturns("disk-cid-456", nil) - - err := act() - Expect(err).ToNot(HaveOccurred()) - - diskName, diskPool, sizeInMB, metadata := director.CreateDynamicDiskArgsForCall(0) - Expect(diskName).To(Equal("my-disk")) - Expect(diskPool).To(Equal("large")) - Expect(sizeInMB).To(Equal(102400)) - Expect(metadata).To(BeNil()) - - Expect(ui.Said).To(ContainElement(ContainSubstring("my-disk"))) - }) - - It("returns error if creation fails", func() { - director.CreateDynamicDiskReturns("", errors.New("fake-err")) - - err := act() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("fake-err")) - }) - }) -}) diff --git a/cmd/delete_disk.go b/cmd/delete_disk.go index d32d1cdda..d65a27238 100644 --- a/cmd/delete_disk.go +++ b/cmd/delete_disk.go @@ -21,6 +21,10 @@ func (c DeleteDiskCmd) Run(opts DeleteDiskOpts) error { return err } + if opts.Dynamic { + return c.director.DeleteDynamicDisk(opts.Args.CID) + } + disk, err := c.director.FindOrphanDisk(opts.Args.CID) if err != nil { return err diff --git a/cmd/delete_disk_test.go b/cmd/delete_disk_test.go index 3b43c2c9a..3ea5a4b7a 100644 --- a/cmd/delete_disk_test.go +++ b/cmd/delete_disk_test.go @@ -80,5 +80,39 @@ var _ = Describe("DeleteDiskCmd", func() { Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-err")) }) + + Context("when --dynamic", func() { + BeforeEach(func() { + deleteDiskOpts = opts.DeleteDiskOpts{ + Args: opts.DeleteDiskArgs{CID: "my-disk"}, + Dynamic: true, + } + }) + + It("deletes the dynamic disk", func() { + err := act() + Expect(err).ToNot(HaveOccurred()) + + Expect(director.DeleteDynamicDiskArgsForCall(0)).To(Equal("my-disk")) + }) + + It("returns error if deletion fails", func() { + director.DeleteDynamicDiskReturns(errors.New("fake-err")) + + err := act() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("fake-err")) + }) + + It("does not delete if confirmation is rejected", func() { + ui.AskedConfirmationErr = errors.New("stop") + + err := act() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("stop")) + + Expect(director.DeleteDynamicDiskCallCount()).To(Equal(0)) + }) + }) }) }) diff --git a/cmd/delete_dynamic_disk.go b/cmd/delete_dynamic_disk.go deleted file mode 100644 index cc7aaf5e2..000000000 --- a/cmd/delete_dynamic_disk.go +++ /dev/null @@ -1,25 +0,0 @@ -package cmd - -import ( - . "github.com/cloudfoundry/bosh-cli/v7/cmd/opts" //nolint:staticcheck - boshdir "github.com/cloudfoundry/bosh-cli/v7/director" - boshui "github.com/cloudfoundry/bosh-cli/v7/ui" -) - -type DeleteDynamicDiskCmd struct { - ui boshui.UI - director boshdir.Director -} - -func NewDeleteDynamicDiskCmd(ui boshui.UI, director boshdir.Director) DeleteDynamicDiskCmd { - return DeleteDynamicDiskCmd{ui: ui, director: director} -} - -func (c DeleteDynamicDiskCmd) Run(opts DeleteDynamicDiskOpts) error { - err := c.ui.AskForConfirmation() - if err != nil { - return err - } - - return c.director.DeleteDynamicDisk(opts.Args.DiskName) -} diff --git a/cmd/delete_dynamic_disk_test.go b/cmd/delete_dynamic_disk_test.go deleted file mode 100644 index f43df4b13..000000000 --- a/cmd/delete_dynamic_disk_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package cmd_test - -import ( - "errors" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/cloudfoundry/bosh-cli/v7/cmd" - "github.com/cloudfoundry/bosh-cli/v7/cmd/opts" - fakedir "github.com/cloudfoundry/bosh-cli/v7/director/directorfakes" - fakeui "github.com/cloudfoundry/bosh-cli/v7/ui/fakes" -) - -var _ = Describe("DeleteDynamicDiskCmd", func() { - var ( - ui *fakeui.FakeUI - director *fakedir.FakeDirector - command cmd.DeleteDynamicDiskCmd - ) - - BeforeEach(func() { - ui = &fakeui.FakeUI{} - director = &fakedir.FakeDirector{} - command = cmd.NewDeleteDynamicDiskCmd(ui, director) - }) - - Describe("Run", func() { - var deleteOpts opts.DeleteDynamicDiskOpts - - BeforeEach(func() { - deleteOpts = opts.DeleteDynamicDiskOpts{ - Args: opts.DeleteDynamicDiskArgs{DiskName: "my-disk"}, - } - }) - - act := func() error { return command.Run(deleteOpts) } - - It("deletes the dynamic disk", func() { - err := act() - Expect(err).ToNot(HaveOccurred()) - - Expect(director.DeleteDynamicDiskArgsForCall(0)).To(Equal("my-disk")) - }) - - It("returns error if deletion fails", func() { - director.DeleteDynamicDiskReturns(errors.New("fake-err")) - - err := act() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("fake-err")) - }) - - It("does not delete if confirmation is rejected", func() { - ui.AskedConfirmationErr = errors.New("stop") - - err := act() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("stop")) - - Expect(director.DeleteDynamicDiskCallCount()).To(Equal(0)) - }) - }) -}) diff --git a/cmd/detach_dynamic_disk.go b/cmd/detach_dynamic_disk.go deleted file mode 100644 index ea64f36b0..000000000 --- a/cmd/detach_dynamic_disk.go +++ /dev/null @@ -1,25 +0,0 @@ -package cmd - -import ( - . "github.com/cloudfoundry/bosh-cli/v7/cmd/opts" //nolint:staticcheck - boshdir "github.com/cloudfoundry/bosh-cli/v7/director" - boshui "github.com/cloudfoundry/bosh-cli/v7/ui" -) - -type DetachDynamicDiskCmd struct { - ui boshui.UI - director boshdir.Director -} - -func NewDetachDynamicDiskCmd(ui boshui.UI, director boshdir.Director) DetachDynamicDiskCmd { - return DetachDynamicDiskCmd{ui: ui, director: director} -} - -func (c DetachDynamicDiskCmd) Run(opts DetachDynamicDiskOpts) error { - err := c.ui.AskForConfirmation() - if err != nil { - return err - } - - return c.director.DetachDynamicDisk(opts.Args.DiskName) -} diff --git a/cmd/detach_dynamic_disk_test.go b/cmd/detach_dynamic_disk_test.go deleted file mode 100644 index 10d8163ee..000000000 --- a/cmd/detach_dynamic_disk_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package cmd_test - -import ( - "errors" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/cloudfoundry/bosh-cli/v7/cmd" - "github.com/cloudfoundry/bosh-cli/v7/cmd/opts" - fakedir "github.com/cloudfoundry/bosh-cli/v7/director/directorfakes" - fakeui "github.com/cloudfoundry/bosh-cli/v7/ui/fakes" -) - -var _ = Describe("DetachDynamicDiskCmd", func() { - var ( - ui *fakeui.FakeUI - director *fakedir.FakeDirector - command cmd.DetachDynamicDiskCmd - ) - - BeforeEach(func() { - ui = &fakeui.FakeUI{} - director = &fakedir.FakeDirector{} - command = cmd.NewDetachDynamicDiskCmd(ui, director) - }) - - Describe("Run", func() { - var detachOpts opts.DetachDynamicDiskOpts - - BeforeEach(func() { - detachOpts = opts.DetachDynamicDiskOpts{ - Args: opts.DetachDynamicDiskArgs{DiskName: "my-disk"}, - } - }) - - act := func() error { return command.Run(detachOpts) } - - It("detaches the dynamic disk", func() { - err := act() - Expect(err).ToNot(HaveOccurred()) - - Expect(director.DetachDynamicDiskArgsForCall(0)).To(Equal("my-disk")) - }) - - It("returns error if detaching fails", func() { - director.DetachDynamicDiskReturns(errors.New("fake-err")) - - err := act() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("fake-err")) - }) - - It("does not detach if confirmation is rejected", func() { - ui.AskedConfirmationErr = errors.New("stop") - - err := act() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("stop")) - - Expect(director.DetachDynamicDiskCallCount()).To(Equal(0)) - }) - }) -}) diff --git a/cmd/opts/opts.go b/cmd/opts/opts.go index 3f5ded480..a9a9fdd4d 100644 --- a/cmd/opts/opts.go +++ b/cmd/opts/opts.go @@ -123,13 +123,6 @@ type BoshOpts struct { DeleteDisk DeleteDiskOpts `command:"delete-disk" description:"Delete disk"` OrphanDisk OrphanDiskOpts `command:"orphan-disk" description:"Orphan disk"` - // Dynamic Disks - ProvideDisk ProvideDiskOpts `command:"provide-disk" description:"Provide (create+attach) a dynamic disk to a VM"` - DetachDynamicDisk DetachDynamicDiskOpts `command:"detach-dynamic-disk" description:"Detach a dynamic disk from its VM"` - DeleteDynamicDisk DeleteDynamicDiskOpts `command:"delete-dynamic-disk" description:"Delete a dynamic disk"` - CreateDynamicDisk CreateDynamicDiskOpts `command:"create-dynamic-disk" description:"Create a dynamic disk without attaching it"` - AttachDynamicDisk AttachDynamicDiskOpts `command:"attach-dynamic-disk" description:"Attach an existing dynamic disk to a VM"` - // Networks Networks NetworksOpts `command:"networks" description:"List networks"` DeleteNetwork DeleteNetworkOpts `command:"delete-network" description:"Delete network"` @@ -752,12 +745,13 @@ type DeleteNetworkArgs struct { type DisksOpts struct { Orphaned bool `long:"orphaned" short:"o" description:"List orphaned disks"` - Dynamic bool `long:"dynamic" short:"d" description:"List dynamic disks"` + Dynamic bool `long:"dynamic" description:"List dynamic disks"` cmd } type DeleteDiskOpts struct { - Args DeleteDiskArgs `positional-args:"true" required:"true"` + Args DeleteDiskArgs `positional-args:"true" required:"true"` + Dynamic bool `long:"dynamic" description:"Delete a dynamic disk by name"` cmd } @@ -773,63 +767,6 @@ type OrphanDiskArgs struct { CID string `positional-arg-name:"CID"` } -// Dynamic Disks - -type ProvideDiskOpts struct { - Args ProvideDiskArgs `positional-args:"true" required:"true"` - - DiskPool string `long:"disk-pool" short:"p" description:"Disk pool / type name to use when creating the disk" required:"true"` - Size int `long:"size" short:"s" description:"Disk size in MB" required:"true"` - - cmd -} - -type ProvideDiskArgs struct { - DiskName string `positional-arg-name:"DISK-NAME"` - InstanceID boshdir.InstanceSlug `positional-arg-name:"INSTANCE-GROUP/INSTANCE-ID"` -} - -type DetachDynamicDiskOpts struct { - Args DetachDynamicDiskArgs `positional-args:"true" required:"true"` - cmd -} - -type DetachDynamicDiskArgs struct { - DiskName string `positional-arg-name:"DISK-NAME"` -} - -type DeleteDynamicDiskOpts struct { - Args DeleteDynamicDiskArgs `positional-args:"true" required:"true"` - cmd -} - -type DeleteDynamicDiskArgs struct { - DiskName string `positional-arg-name:"DISK-NAME"` -} - -type CreateDynamicDiskOpts struct { - Args CreateDynamicDiskArgs `positional-args:"true" required:"true"` - - DiskPool string `long:"disk-pool" short:"p" description:"Disk pool / type name to use when creating the disk" required:"true"` - Size int `long:"size" short:"s" description:"Disk size in MB" required:"true"` - - cmd -} - -type CreateDynamicDiskArgs struct { - DiskName string `positional-arg-name:"DISK-NAME"` -} - -type AttachDynamicDiskOpts struct { - Args AttachDynamicDiskArgs `positional-args:"true" required:"true"` - cmd -} - -type AttachDynamicDiskArgs struct { - DiskName string `positional-arg-name:"DISK-NAME"` - InstanceID boshdir.InstanceSlug `positional-arg-name:"INSTANCE-GROUP/INSTANCE-ID"` -} - // Snapshots type SnapshotsOpts struct { diff --git a/cmd/opts/opts_test.go b/cmd/opts/opts_test.go index bccf004b8..d596a43fe 100644 --- a/cmd/opts/opts_test.go +++ b/cmd/opts/opts_test.go @@ -2219,6 +2219,14 @@ var _ = Describe("Opts", func() { Expect(getStructTagForName("Args", opts)).To(Equal(`positional-args:"true" required:"true"`)) }) }) + + Describe("Dynamic", func() { + It("contains desired values", func() { + Expect(getStructTagForName("Dynamic", opts)).To(Equal( + `long:"dynamic" description:"Delete a dynamic disk by name"`, + )) + }) + }) }) Describe("DeleteDiskArgs", func() { diff --git a/cmd/provide_disk.go b/cmd/provide_disk.go deleted file mode 100644 index 7eca3455c..000000000 --- a/cmd/provide_disk.go +++ /dev/null @@ -1,29 +0,0 @@ -package cmd - -import ( - . "github.com/cloudfoundry/bosh-cli/v7/cmd/opts" //nolint:staticcheck - boshdir "github.com/cloudfoundry/bosh-cli/v7/director" - boshui "github.com/cloudfoundry/bosh-cli/v7/ui" -) - -type ProvideDiskCmd struct { - ui boshui.UI - director boshdir.Director -} - -func NewProvideDiskCmd(ui boshui.UI, director boshdir.Director) ProvideDiskCmd { - return ProvideDiskCmd{ui: ui, director: director} -} - -func (c ProvideDiskCmd) Run(opts ProvideDiskOpts) error { - instanceID := opts.Args.InstanceID.String() - diskName := opts.Args.DiskName - - diskCID, err := c.director.ProvideDynamicDisk(instanceID, diskName, opts.DiskPool, opts.Size, nil) - if err != nil { - return err - } - - c.ui.PrintLinef("Disk '%s' provided (CID: %s)", diskName, diskCID) - return nil -} diff --git a/cmd/provide_disk_test.go b/cmd/provide_disk_test.go deleted file mode 100644 index 0184f8c2d..000000000 --- a/cmd/provide_disk_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package cmd_test - -import ( - "errors" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/cloudfoundry/bosh-cli/v7/cmd" - "github.com/cloudfoundry/bosh-cli/v7/cmd/opts" - boshdir "github.com/cloudfoundry/bosh-cli/v7/director" - fakedir "github.com/cloudfoundry/bosh-cli/v7/director/directorfakes" - fakeui "github.com/cloudfoundry/bosh-cli/v7/ui/fakes" -) - -var _ = Describe("ProvideDiskCmd", func() { - var ( - ui *fakeui.FakeUI - director *fakedir.FakeDirector - command cmd.ProvideDiskCmd - ) - - BeforeEach(func() { - ui = &fakeui.FakeUI{} - director = &fakedir.FakeDirector{} - command = cmd.NewProvideDiskCmd(ui, director) - }) - - Describe("Run", func() { - var provideDiskOpts opts.ProvideDiskOpts - - BeforeEach(func() { - slug := boshdir.NewInstanceSlug("web", "abc123") - - provideDiskOpts = opts.ProvideDiskOpts{ - Args: opts.ProvideDiskArgs{ - DiskName: "my-disk", - InstanceID: slug, - }, - DiskPool: "large", - Size: 51200, - } - }) - - act := func() error { return command.Run(provideDiskOpts) } - - It("provides (create+attach) a dynamic disk", func() { - director.ProvideDynamicDiskReturns("disk-cid-123", nil) - - err := act() - Expect(err).ToNot(HaveOccurred()) - - instanceID, diskName, diskPool, sizeInMB, metadata := director.ProvideDynamicDiskArgsForCall(0) - Expect(instanceID).To(Equal("web/abc123")) - Expect(diskName).To(Equal("my-disk")) - Expect(diskPool).To(Equal("large")) - Expect(sizeInMB).To(Equal(51200)) - Expect(metadata).To(BeNil()) - - Expect(ui.Said).To(ContainElement(ContainSubstring("my-disk"))) - }) - - It("returns error if providing disk failed", func() { - director.ProvideDynamicDiskReturns("", errors.New("fake-err")) - - err := act() - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("fake-err")) - }) - }) -}) From 0541a50c06de93c0c70a291f962bff9b34016cad Mon Sep 17 00:00:00 2001 From: Nishad Mathur Date: Mon, 22 Jun 2026 17:04:43 -0700 Subject: [PATCH 3/3] Add disk_pool_name to dynamic disk list response - Add DiskPoolName() to DynamicDisk interface and DynamicDiskImpl - Include disk_pool_name field in DynamicDiskResp (GET /dynamic_disks) - Show Disk Pool column in `bosh disks --dynamic` table output - Update FakeDynamicDisk with DiskPoolName() counterfeiter methods [TNZ-99509] [TNZ-109499] Co-authored-by: Cursor --- cmd/disks.go | 2 + cmd/disks_test.go | 3 + director/directorfakes/fake_dynamic_disk.go | 63 +++++++++++++++++++++ director/dynamic_disks.go | 4 ++ director/interfaces.go | 1 + 5 files changed, 73 insertions(+) diff --git a/cmd/disks.go b/cmd/disks.go index 26ac7888c..e5ecbe817 100644 --- a/cmd/disks.go +++ b/cmd/disks.go @@ -73,6 +73,7 @@ func (c DisksCmd) runDynamic() error { boshtbl.NewHeader("Name"), boshtbl.NewHeader("Disk CID"), boshtbl.NewHeader("Size"), + boshtbl.NewHeader("Disk Pool"), boshtbl.NewHeader("Deployment"), boshtbl.NewHeader("Instance"), boshtbl.NewHeader("AZ"), @@ -86,6 +87,7 @@ func (c DisksCmd) runDynamic() error { boshtbl.NewValueString(d.Name()), boshtbl.NewValueString(d.DiskCID()), boshtbl.NewValueMegaBytes(d.Size()), + boshtbl.NewValueString(d.DiskPoolName()), boshtbl.NewValueString(d.DeploymentName()), boshtbl.NewValueString(d.InstanceName()), boshtbl.NewValueString(d.AvailabilityZone()), diff --git a/cmd/disks_test.go b/cmd/disks_test.go index 0696948d6..859ddc927 100644 --- a/cmd/disks_test.go +++ b/cmd/disks_test.go @@ -120,6 +120,7 @@ var _ = Describe("DisksCmd", func() { NameStub: func() string { return "my-disk" }, DiskCIDStub: func() string { return "disk-cid-1" }, SizeStub: func() uint64 { return 2048 }, + DiskPoolNameStub: func() string { return "large" }, DeploymentNameStub: func() string { return "my-deployment" }, InstanceNameStub: func() string { return "api/abc123" }, AvailabilityZoneStub: func() string { return "z1" }, @@ -139,6 +140,7 @@ var _ = Describe("DisksCmd", func() { boshtbl.NewHeader("Name"), boshtbl.NewHeader("Disk CID"), boshtbl.NewHeader("Size"), + boshtbl.NewHeader("Disk Pool"), boshtbl.NewHeader("Deployment"), boshtbl.NewHeader("Instance"), boshtbl.NewHeader("AZ"), @@ -152,6 +154,7 @@ var _ = Describe("DisksCmd", func() { boshtbl.NewValueString("my-disk"), boshtbl.NewValueString("disk-cid-1"), boshtbl.NewValueMegaBytes(2048), + boshtbl.NewValueString("large"), boshtbl.NewValueString("my-deployment"), boshtbl.NewValueString("api/abc123"), boshtbl.NewValueString("z1"), diff --git a/director/directorfakes/fake_dynamic_disk.go b/director/directorfakes/fake_dynamic_disk.go index 4ea3ac2c9..feace2d98 100644 --- a/director/directorfakes/fake_dynamic_disk.go +++ b/director/directorfakes/fake_dynamic_disk.go @@ -48,6 +48,16 @@ type FakeDynamicDisk struct { diskCIDReturnsOnCall map[int]struct { result1 string } + DiskPoolNameStub func() string + diskPoolNameMutex sync.RWMutex + diskPoolNameArgsForCall []struct { + } + diskPoolNameReturns struct { + result1 string + } + diskPoolNameReturnsOnCall map[int]struct { + result1 string + } InstanceNameStub func() string instanceNameMutex sync.RWMutex instanceNameArgsForCall []struct { @@ -294,6 +304,59 @@ func (fake *FakeDynamicDisk) DiskCIDReturnsOnCall(i int, result1 string) { }{result1} } +func (fake *FakeDynamicDisk) DiskPoolName() string { + fake.diskPoolNameMutex.Lock() + ret, specificReturn := fake.diskPoolNameReturnsOnCall[len(fake.diskPoolNameArgsForCall)] + fake.diskPoolNameArgsForCall = append(fake.diskPoolNameArgsForCall, struct { + }{}) + stub := fake.DiskPoolNameStub + fakeReturns := fake.diskPoolNameReturns + fake.recordInvocation("DiskPoolName", []interface{}{}) + fake.diskPoolNameMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeDynamicDisk) DiskPoolNameCallCount() int { + fake.diskPoolNameMutex.RLock() + defer fake.diskPoolNameMutex.RUnlock() + return len(fake.diskPoolNameArgsForCall) +} + +func (fake *FakeDynamicDisk) DiskPoolNameCalls(stub func() string) { + fake.diskPoolNameMutex.Lock() + defer fake.diskPoolNameMutex.Unlock() + fake.DiskPoolNameStub = stub +} + +func (fake *FakeDynamicDisk) DiskPoolNameReturns(result1 string) { + fake.diskPoolNameMutex.Lock() + defer fake.diskPoolNameMutex.Unlock() + fake.DiskPoolNameStub = nil + fake.diskPoolNameReturns = struct { + result1 string + }{result1} +} + +func (fake *FakeDynamicDisk) DiskPoolNameReturnsOnCall(i int, result1 string) { + fake.diskPoolNameMutex.Lock() + defer fake.diskPoolNameMutex.Unlock() + fake.DiskPoolNameStub = nil + if fake.diskPoolNameReturnsOnCall == nil { + fake.diskPoolNameReturnsOnCall = make(map[int]struct { + result1 string + }) + } + fake.diskPoolNameReturnsOnCall[i] = struct { + result1 string + }{result1} +} + func (fake *FakeDynamicDisk) InstanceName() string { fake.instanceNameMutex.Lock() ret, specificReturn := fake.instanceNameReturnsOnCall[len(fake.instanceNameArgsForCall)] diff --git a/director/dynamic_disks.go b/director/dynamic_disks.go index d60a25620..a95bb5eec 100644 --- a/director/dynamic_disks.go +++ b/director/dynamic_disks.go @@ -17,6 +17,7 @@ type DynamicDiskImpl struct { instanceName string availabilityZone string size uint64 + diskPoolName string cpi string } @@ -26,6 +27,7 @@ func (d DynamicDiskImpl) DeploymentName() string { return d.deploymentName } func (d DynamicDiskImpl) InstanceName() string { return d.instanceName } func (d DynamicDiskImpl) AvailabilityZone() string { return d.availabilityZone } func (d DynamicDiskImpl) Size() uint64 { return d.size } +func (d DynamicDiskImpl) DiskPoolName() string { return d.diskPoolName } func (d DynamicDiskImpl) CPI() string { return d.cpi } type DynamicDiskResp struct { @@ -35,6 +37,7 @@ type DynamicDiskResp struct { Instance string `json:"instance"` AvailabilityZone string `json:"availability_zone"` Size uint64 `json:"size"` + DiskPoolName string `json:"disk_pool_name"` CPI string `json:"cpi"` } @@ -134,6 +137,7 @@ func (c Client) DynamicDisks() ([]DynamicDisk, error) { instanceName: r.Instance, availabilityZone: r.AvailabilityZone, size: r.Size, + diskPoolName: r.DiskPoolName, cpi: r.CPI, }) } diff --git a/director/interfaces.go b/director/interfaces.go index 3ee17c2bb..3455e2ace 100644 --- a/director/interfaces.go +++ b/director/interfaces.go @@ -339,6 +339,7 @@ type DynamicDisk interface { InstanceName() string AvailabilityZone() string Size() uint64 + DiskPoolName() string CPI() string }