From efaf3e2cd0f410a2c56b90c49d32c8547941f8bb Mon Sep 17 00:00:00 2001 From: dgupta Date: Wed, 5 Feb 2025 19:09:50 +0530 Subject: [PATCH 1/2] Support delete and update operation --- commands/partner_interconnect_attachments.go | 95 +++++++++++++++++++ .../partner_interconnect_attachments_test.go | 16 +++- do/mocks/VPCsService.go | 14 +++ do/vpcs.go | 6 ++ 4 files changed, 130 insertions(+), 1 deletion(-) diff --git a/commands/partner_interconnect_attachments.go b/commands/partner_interconnect_attachments.go index dc6d43792..f4f5e595b 100644 --- a/commands/partner_interconnect_attachments.go +++ b/commands/partner_interconnect_attachments.go @@ -2,7 +2,9 @@ package commands import ( "fmt" + "os" "strings" + "time" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -66,6 +68,11 @@ With the Partner Interconnect Attachments commands, you can get or list, create, aliasOpt("ls"), displayerType(&displayers.PartnerInterconnectAttachment{})) cmdPartnerIAList.Example = `The following example lists the Network Interconnect Attachments on your account : doctl network interconnect-attachment list --format Name,VPCIDs` + cmdPartnerIADelete := CmdBuilder(cmd, RunPartnerInterconnectAttachmentDelete, "delete ", + "Deletes a Partner Interconnect Attachment", "Deletes information about a Partner Interconnect Attachment. This is irreversible ", Writer, + aliasOpt("g"), displayerType(&displayers.PartnerInterconnectAttachment{})) + cmdPartnerIADelete.Example = `The following example deletes a Partner Interconnect Attachment with the ID ` + "`" + `f81d4fae-7dec-11d0-a765-00a0c91e6bf6` + "`" + `: doctl partner interconnect-attachment delete f81d4fae-7dec-11d0-a765-00a0c91e6bf6` + return cmd } @@ -119,3 +126,91 @@ func RunPartnerInterconnectAttachmentList(c *CmdConfig) error { item := &displayers.PartnerInterconnectAttachment{PartnerInterconnectAttachments: list} return c.Display(item) } + +// RunPartnerInterconnectAttachmentDelete deletes an existing Partner Interconnect Attachment by its identifier. +func RunPartnerInterconnectAttachmentDelete(c *CmdConfig) error { + + if err := ensurePartnerAttachmentType(c); err != nil { + return err + } + + err := ensureOneArg(c) + if err != nil { + return err + } + iaID := c.Args[0] + + force, err := c.Doit.GetBool(c.NS, doctl.ArgForce) + if err != nil { + return err + } + + if force || AskForConfirmDelete("Partner Interconnect Attachment", 1) == nil { + + vpcs := c.VPCs() + err := vpcs.DeletePartnerInterconnectAttachment(iaID) + if err != nil { + return err + } + + wait, err := c.Doit.GetBool(c.NS, doctl.ArgCommandWait) + if err != nil { + return err + } + + if wait { + notice("Partner Interconnect Attachment is in progress, waiting for Partner Interconnect Attachment to be deleted") + + err := waitForPIA(vpcs, iaID, "DELETED", true) + if err != nil { + return fmt.Errorf("Partner Interconnect Attachment couldn't be deleted : %v", err) + } + notice("Partner Interconnect Attachment is successfully deleted") + } else { + notice("Partner Interconnect Attachment deletion request accepted") + } + + } else { + return fmt.Errorf("operation aborted") + } + + return nil +} + +func waitForPIA(vpcService do.VPCsService, iaID string, wantStatus string, terminateOnNotFound bool) error { + const maxAttempts = 360 + const errStatus = "ERROR" + attempts := 0 + printNewLineSet := false + + for i := 0; i < maxAttempts; i++ { + if attempts != 0 { + fmt.Fprint(os.Stderr, ".") + if !printNewLineSet { + printNewLineSet = true + defer fmt.Fprintln(os.Stderr) + } + } + + interconnectAttachment, err := vpcService.GetPartnerInterconnectAttachment(iaID) + if err != nil { + if terminateOnNotFound && strings.Contains(err.Error(), "not found") { + return nil + } + return err + } + + if interconnectAttachment.State == errStatus { + return fmt.Errorf("Partner Interconnect Attachment (%s) entered status `%s`", iaID, errStatus) + } + + if interconnectAttachment.State == wantStatus { + return nil + } + + attempts++ + time.Sleep(5 * time.Second) + } + + return fmt.Errorf("timeout waiting for Partner Interconnect Attachment (%s) to become %s", iaID, wantStatus) +} diff --git a/commands/partner_interconnect_attachments_test.go b/commands/partner_interconnect_attachments_test.go index 1d133a00b..732210786 100644 --- a/commands/partner_interconnect_attachments_test.go +++ b/commands/partner_interconnect_attachments_test.go @@ -26,7 +26,7 @@ var ( func TestInterconnectAttachmentsCommand(t *testing.T) { cmd := PartnerInterconnectAttachments() assert.NotNil(t, cmd) - assertCommandNames(t, cmd, "get", "list") + assertCommandNames(t, cmd, "get", "list", "delete") } func TestInterconnectAttachmentsGet(t *testing.T) { @@ -59,3 +59,17 @@ func TestInterconnectAttachmentsList(t *testing.T) { assert.NoError(t, err) }) } + +func TestInterconnectAttachmentsDelete(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + iaID := "e819b321-a9a1-4078-b437-8e6b8bf13530" + tm.vpcs.EXPECT().DeletePartnerInterconnectAttachment(iaID).Return(nil) + + config.Args = append(config.Args, iaID) + config.Doit.Set("network", doctl.ArgInterconnectAttachmentType, "partner") + config.Doit.Set(config.NS, doctl.ArgForce, true) + + err := RunPartnerInterconnectAttachmentDelete(config) + assert.NoError(t, err) + }) +} diff --git a/do/mocks/VPCsService.go b/do/mocks/VPCsService.go index b99484b4a..ba62f20de 100644 --- a/do/mocks/VPCsService.go +++ b/do/mocks/VPCsService.go @@ -85,6 +85,20 @@ func (mr *MockVPCsServiceMockRecorder) Delete(vpcUUID any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockVPCsService)(nil).Delete), vpcUUID) } +// DeletePartnerInterconnectAttachment mocks base method. +func (m *MockVPCsService) DeletePartnerInterconnectAttachment(iaID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeletePartnerInterconnectAttachment", iaID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeletePartnerInterconnectAttachment indicates an expected call of DeletePartnerInterconnectAttachment. +func (mr *MockVPCsServiceMockRecorder) DeletePartnerInterconnectAttachment(iaID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePartnerInterconnectAttachment", reflect.TypeOf((*MockVPCsService)(nil).DeletePartnerInterconnectAttachment), iaID) +} + // DeleteVPCPeering mocks base method. func (m *MockVPCsService) DeleteVPCPeering(peeringID string) error { m.ctrl.T.Helper() diff --git a/do/vpcs.go b/do/vpcs.go index a483a2424..1c1d6ab5c 100644 --- a/do/vpcs.go +++ b/do/vpcs.go @@ -59,6 +59,7 @@ type VPCsService interface { ListVPCPeeringsByVPCID(vpcID string) (VPCPeerings, error) GetPartnerInterconnectAttachment(iaID string) (*PartnerInterconnectAttachment, error) ListPartnerInterconnectAttachments() (PartnerInterconnectAttachments, error) + DeletePartnerInterconnectAttachment(iaID string) error } var _ VPCsService = &vpcsService{} @@ -268,3 +269,8 @@ func (v *vpcsService) ListPartnerInterconnectAttachments() (PartnerInterconnectA return list, nil } + +func (v *vpcsService) DeletePartnerInterconnectAttachment(iaID string) error { + _, err := v.client.PartnerInterconnectAttachments.Delete(context.TODO(), iaID) + return err +} From 7c5822ae9eb1c722391dd420136f4eec9ee59d3c Mon Sep 17 00:00:00 2001 From: dgupta Date: Wed, 5 Feb 2025 19:59:32 +0530 Subject: [PATCH 2/2] add update method --- args.go | 4 ++ commands/partner_interconnect_attachments.go | 52 ++++++++++++++++++- .../partner_interconnect_attachments_test.go | 34 +++++++++++- do/mocks/VPCsService.go | 15 ++++++ do/vpcs.go | 10 ++++ 5 files changed, 111 insertions(+), 4 deletions(-) diff --git a/args.go b/args.go index 43599d5ea..787fb3f5e 100644 --- a/args.go +++ b/args.go @@ -525,6 +525,10 @@ const ( // ArgInterconnectAttachmentType is the type of the Interconnect Attachment e.g. "partner". ArgInterconnectAttachmentType = "type" + // ArgPartnerInterconnectAttachmentName is a name of the Partner Interconnect Attachment. + ArgPartnerInterconnectAttachmentName = "name" + // ArgPartnerInterconnectAttachmentVPCIDs are the IDs of the VPCs which the Partner Interconnect Attachment is connected + ArgPartnerInterconnectAttachmentVPCIDs = "vpc-ids" // ArgReadWrite indicates a generated token should be read/write. ArgReadWrite = "read-write" diff --git a/commands/partner_interconnect_attachments.go b/commands/partner_interconnect_attachments.go index f4f5e595b..643ee4df3 100644 --- a/commands/partner_interconnect_attachments.go +++ b/commands/partner_interconnect_attachments.go @@ -12,6 +12,7 @@ import ( "github.com/digitalocean/doctl" "github.com/digitalocean/doctl/commands/displayers" "github.com/digitalocean/doctl/do" + "github.com/digitalocean/godo" ) // Network creates the partner commands @@ -70,8 +71,19 @@ With the Partner Interconnect Attachments commands, you can get or list, create, cmdPartnerIADelete := CmdBuilder(cmd, RunPartnerInterconnectAttachmentDelete, "delete ", "Deletes a Partner Interconnect Attachment", "Deletes information about a Partner Interconnect Attachment. This is irreversible ", Writer, - aliasOpt("g"), displayerType(&displayers.PartnerInterconnectAttachment{})) - cmdPartnerIADelete.Example = `The following example deletes a Partner Interconnect Attachment with the ID ` + "`" + `f81d4fae-7dec-11d0-a765-00a0c91e6bf6` + "`" + `: doctl partner interconnect-attachment delete f81d4fae-7dec-11d0-a765-00a0c91e6bf6` + aliasOpt("rm"), displayerType(&displayers.PartnerInterconnectAttachment{})) + cmdPartnerIADelete.Example = `The following example deletes a Partner Interconnect Attachment with the ID ` + "`" + `f81d4fae-7dec-11d0-a765-00a0c91e6bf6` + "`" + + `: doctl network --type "partner" interconnect-attachment delete f81d4fae-7dec-11d0-a765-00a0c91e6bf6` + + cmdPartnerIAUpdate := CmdBuilder(cmd, RunPartnerInterconnectAttachmentUpdate, "update ", + "Update a Partner Interconnect Attachment's name and configuration", `Use this command to update the name and and configuration of a Partner Interconnect Attachment`, Writer, aliasOpt("u")) + AddStringFlag(cmdPartnerIAUpdate, doctl.ArgPartnerInterconnectAttachmentName, "", "", + "The Partner Interconnect Attachment's name", requiredOpt()) + AddStringFlag(cmdPartnerIAUpdate, doctl.ArgPartnerInterconnectAttachmentVPCIDs, "", "", + "The Partner Interconnect Attachment's vpc ids", requiredOpt()) + cmdPartnerIAUpdate.Example = `The following example updates the name of a Partner Interconnect Attachment with the ID ` + + "`" + `f81d4fae-7dec-11d0-a765-00a0c91e6bf6` + "`" + ` to ` + "`" + `new-name` + "`" + + `: doctl network --type "partner" interconnect-attachment update f81d4fae-7dec-11d0-a765-00a0c91e6bf6 --name new-name` return cmd } @@ -127,6 +139,42 @@ func RunPartnerInterconnectAttachmentList(c *CmdConfig) error { return c.Display(item) } +// RunPartnerInterconnectAttachmentUpdate updates an existing Partner Interconnect Attachment with new configuration. +func RunPartnerInterconnectAttachmentUpdate(c *CmdConfig) error { + if err := ensurePartnerAttachmentType(c); err != nil { + return err + } + + err := ensureOneArg(c) + if err != nil { + return err + } + peeringID := c.Args[0] + + r := new(godo.PartnerInterconnectAttachmentUpdateRequest) + name, err := c.Doit.GetString(c.NS, doctl.ArgPartnerInterconnectAttachmentName) + if err != nil { + return err + } + r.Name = name + + vpcIDs, err := c.Doit.GetString(c.NS, doctl.ArgPartnerInterconnectAttachmentVPCIDs) + if err != nil { + return err + } + r.VPCIDs = strings.Split(vpcIDs, ",") + + interconnectAttachment, err := c.VPCs().UpdatePartnerInterconnectAttachment(peeringID, r) + if err != nil { + return err + } + + item := &displayers.PartnerInterconnectAttachment{ + PartnerInterconnectAttachments: do.PartnerInterconnectAttachments{*interconnectAttachment}, + } + return c.Display(item) +} + // RunPartnerInterconnectAttachmentDelete deletes an existing Partner Interconnect Attachment by its identifier. func RunPartnerInterconnectAttachmentDelete(c *CmdConfig) error { diff --git a/commands/partner_interconnect_attachments_test.go b/commands/partner_interconnect_attachments_test.go index 732210786..63afb93b5 100644 --- a/commands/partner_interconnect_attachments_test.go +++ b/commands/partner_interconnect_attachments_test.go @@ -1,6 +1,7 @@ package commands import ( + "strings" "testing" "github.com/digitalocean/godo" @@ -26,7 +27,7 @@ var ( func TestInterconnectAttachmentsCommand(t *testing.T) { cmd := PartnerInterconnectAttachments() assert.NotNil(t, cmd) - assertCommandNames(t, cmd, "get", "list", "delete") + assertCommandNames(t, cmd, "get", "list", "delete", "update") } func TestInterconnectAttachmentsGet(t *testing.T) { @@ -62,14 +63,43 @@ func TestInterconnectAttachmentsList(t *testing.T) { func TestInterconnectAttachmentsDelete(t *testing.T) { withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + config.Doit.Set("network", doctl.ArgInterconnectAttachmentType, "partner") + iaID := "e819b321-a9a1-4078-b437-8e6b8bf13530" tm.vpcs.EXPECT().DeletePartnerInterconnectAttachment(iaID).Return(nil) config.Args = append(config.Args, iaID) - config.Doit.Set("network", doctl.ArgInterconnectAttachmentType, "partner") config.Doit.Set(config.NS, doctl.ArgForce, true) err := RunPartnerInterconnectAttachmentDelete(config) assert.NoError(t, err) }) } + +func TestInterconnectAttachmentsUpdate(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + config.Doit.Set("network", doctl.ArgInterconnectAttachmentType, "partner") + + iaID := "ia-uuid1" + iaName := "ia-name" + vpcIDs := "f81d4fae-7dec-11d0-a765-00a0c91e6bf6,3f900b61-30d7-40d8-9711-8c5d6264b268" + r := godo.PartnerInterconnectAttachmentUpdateRequest{Name: iaName, VPCIDs: strings.Split(vpcIDs, ",")} + tm.vpcs.EXPECT().UpdatePartnerInterconnectAttachment(iaID, &r).Return(&testIA, nil) + + config.Args = append(config.Args, iaID) + config.Doit.Set(config.NS, doctl.ArgPartnerInterconnectAttachmentName, iaName) + config.Doit.Set(config.NS, doctl.ArgPartnerInterconnectAttachmentVPCIDs, vpcIDs) + + err := RunPartnerInterconnectAttachmentUpdate(config) + assert.NoError(t, err) + }) +} + +func TestInterconnectAttachmentsUpdateNoID(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + config.Doit.Set("network", doctl.ArgInterconnectAttachmentType, "partner") + + err := RunPartnerInterconnectAttachmentUpdate(config) + assert.Error(t, err) + }) +} diff --git a/do/mocks/VPCsService.go b/do/mocks/VPCsService.go index ba62f20de..ef028f422 100644 --- a/do/mocks/VPCsService.go +++ b/do/mocks/VPCsService.go @@ -253,6 +253,21 @@ func (mr *MockVPCsServiceMockRecorder) Update(vpcUUID, vpcr any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockVPCsService)(nil).Update), vpcUUID, vpcr) } +// UpdatePartnerInterconnectAttachment mocks base method. +func (m *MockVPCsService) UpdatePartnerInterconnectAttachment(iaID string, req *godo.PartnerInterconnectAttachmentUpdateRequest) (*do.PartnerInterconnectAttachment, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdatePartnerInterconnectAttachment", iaID, req) + ret0, _ := ret[0].(*do.PartnerInterconnectAttachment) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdatePartnerInterconnectAttachment indicates an expected call of UpdatePartnerInterconnectAttachment. +func (mr *MockVPCsServiceMockRecorder) UpdatePartnerInterconnectAttachment(iaID, req any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatePartnerInterconnectAttachment", reflect.TypeOf((*MockVPCsService)(nil).UpdatePartnerInterconnectAttachment), iaID, req) +} + // UpdateVPCPeering mocks base method. func (m *MockVPCsService) UpdateVPCPeering(peeringID string, req *godo.VPCPeeringUpdateRequest) (*do.VPCPeering, error) { m.ctrl.T.Helper() diff --git a/do/vpcs.go b/do/vpcs.go index 1c1d6ab5c..eb4689aa1 100644 --- a/do/vpcs.go +++ b/do/vpcs.go @@ -60,6 +60,7 @@ type VPCsService interface { GetPartnerInterconnectAttachment(iaID string) (*PartnerInterconnectAttachment, error) ListPartnerInterconnectAttachments() (PartnerInterconnectAttachments, error) DeletePartnerInterconnectAttachment(iaID string) error + UpdatePartnerInterconnectAttachment(iaID string, req *godo.PartnerInterconnectAttachmentUpdateRequest) (*PartnerInterconnectAttachment, error) } var _ VPCsService = &vpcsService{} @@ -274,3 +275,12 @@ func (v *vpcsService) DeletePartnerInterconnectAttachment(iaID string) error { _, err := v.client.PartnerInterconnectAttachments.Delete(context.TODO(), iaID) return err } + +func (v *vpcsService) UpdatePartnerInterconnectAttachment(iaID string, req *godo.PartnerInterconnectAttachmentUpdateRequest) (*PartnerInterconnectAttachment, error) { + partnerIA, _, err := v.client.PartnerInterconnectAttachments.Update(context.TODO(), iaID, req) + if err != nil { + return nil, err + } + + return &PartnerInterconnectAttachment{PartnerInterconnectAttachment: partnerIA}, nil +}