diff --git a/commands/displayers/partner_interconnect_attachment.go b/commands/displayers/partner_interconnect_attachment.go new file mode 100644 index 000000000..4386e2c3d --- /dev/null +++ b/commands/displayers/partner_interconnect_attachment.go @@ -0,0 +1,76 @@ +package displayers + +import ( + "io" + "strings" + + "github.com/digitalocean/doctl/do" +) + +type PartnerInterconnectAttachment struct { + PartnerInterconnectAttachments do.PartnerInterconnectAttachments +} + +var _ Displayable = &PartnerInterconnectAttachment{} + +func (v *PartnerInterconnectAttachment) JSON(out io.Writer) error { + return writeJSON(v.PartnerInterconnectAttachments, out) +} + +func (v *PartnerInterconnectAttachment) Cols() []string { + return []string{ + "ID", + "Name", + "State", + "ConnectionBandwidthInMbps", + "Region", + "NaaSProvider", + "VPCIDs", + "CreatedAt", + "BGPLocalASN", + "BGPLocalRouterIP", + "BGPPeerASN", + "BGPPeerRouterIP", + } +} + +func (v *PartnerInterconnectAttachment) ColMap() map[string]string { + return map[string]string{ + "ID": "ID", + "Name": "Name", + "State": "State", + "ConnectionBandwidthInMbps": "Connection Bandwidth (MBPS)", + "Region": "Region", + "NaaSProvider": "NaaS Provider", + "VPCIDs": "VPCIDs", + "Created": "Created At", + "BGPLocalASN": "BGP Local ASN", + "BGPLocalRouterIP": "BGP Local Router IP", + "BGPPeerASN": "BGP Peer ASN", + "BGPPeerRouterIP": "BGP Peer Router IP", + } +} + +func (v *PartnerInterconnectAttachment) KV() []map[string]any { + out := make([]map[string]any, 0, len(v.PartnerInterconnectAttachments)) + + for _, ia := range v.PartnerInterconnectAttachments { + o := map[string]any{ + "ID": ia.ID, + "Name": ia.Name, + "State": ia.State, + "ConnectionBandwidthInMbps": ia.ConnectionBandwidthInMbps, + "Region": ia.Region, + "NaaSProvider": ia.NaaSProvider, + "VPCIDs": strings.Join(ia.VPCIDs, ","), + "Created": ia.CreatedAt, + "BGPLocalASN": ia.BGP.LocalASN, + "BGPLocalRouterIP": ia.BGP.LocalRouterIP, + "BGPPeerASN": ia.BGP.PeerASN, + "BGPPeerRouterIP": ia.BGP.PeerRouterIP, + } + out = append(out, o) + } + + return out +} diff --git a/commands/doit.go b/commands/doit.go index f4f42e23f..a947601a7 100644 --- a/commands/doit.go +++ b/commands/doit.go @@ -21,11 +21,12 @@ import ( "strings" "time" - "github.com/digitalocean/doctl" "github.com/fatih/color" "github.com/mattn/go-isatty" "github.com/spf13/cobra" "github.com/spf13/viper" + + "github.com/digitalocean/doctl" ) const ( @@ -184,6 +185,7 @@ func addCommands() { DoitCmd.AddCommand(Version()) DoitCmd.AddCommand(Registry()) DoitCmd.AddCommand(VPCs()) + DoitCmd.AddCommand(Partner()) DoitCmd.AddCommand(OneClicks()) DoitCmd.AddCommand(Monitoring()) DoitCmd.AddCommand(Serverless()) diff --git a/commands/partner_interconnect_attachments.go b/commands/partner_interconnect_attachments.go new file mode 100644 index 000000000..21020aea7 --- /dev/null +++ b/commands/partner_interconnect_attachments.go @@ -0,0 +1,93 @@ +package commands + +import ( + "github.com/spf13/cobra" + + "github.com/digitalocean/doctl/commands/displayers" + "github.com/digitalocean/doctl/do" +) + +// Partner creates the partner commands +func Partner() *Command { + cmd := &Command{ + Command: &cobra.Command{ + Use: "partner", + Short: "Display commands that manage Partner products", + Long: `The commands under ` + "`" + `doctl partner` + "`" + ` are for managing Partner products`, + GroupID: manageResourcesGroup, + }, + } + + cmd.AddCommand(InterconnectAttachments()) + + return cmd +} + +// InterconnectAttachments creates the interconnect attachment command +func InterconnectAttachments() *Command { + cmd := &Command{ + Command: &cobra.Command{ + Use: "interconnect-attachment", + Short: "Display commands that manage Partner Interconnect Attachments", + Long: `The commands under ` + "`" + `doctl partner interconnect-attachment` + "`" + ` are for managing your Partner Interconnect Attachments. +With the Partner Interconnect Attachments commands, you can get or list, create, update, or delete Partner Interconnect Attachments, and manage their configuration details.`, + }, + } + + interconnectAttachmentDetails := ` +- The Partner Interconnect Attachment ID +- The Partner Interconnect Attachment Name +- The Partner Interconnect Attachment State +- The Partner Interconnect Attachment Connection Bandwidth in Mbps +- The Partner Interconnect Attachment Region +- The Partner Interconnect Attachment NaaS Provider +- The Partner Interconnect Attachment VPC network IDs +- The Partner Interconnect Attachment creation date, in ISO8601 combined date and time format +- The Partner Interconnect Attachment BGP Local ASN +- The Partner Interconnect Attachment BGP Local Router IP +- The Partner Interconnect Attachment BGP Peer ASN +- The Partner Interconnect Attachment BGP Peer Router IP +` + + cmdPartnerIAGet := CmdBuilder(cmd, RunPartnerInterconnectAttachmentGet, "get ", + "Retrieves a Partner Interconnect Attachment", "Retrieves information about a Partner Interconnect Attachment, including:"+interconnectAttachmentDetails, Writer, + aliasOpt("g"), displayerType(&displayers.PartnerInterconnectAttachment{})) + cmdPartnerIAGet.Example = `The following example retrieves information about a Partner Interconnect Attachment with the ID ` + "`" + `f81d4fae-7dec-11d0-a765-00a0c91e6bf6` + "`" + `: doctl partner interconnect-attachment get f81d4fae-7dec-11d0-a765-00a0c91e6bf6` + + cmdPartnerIAList := CmdBuilder(cmd, RunPartnerInterconnectAttachmentList, "list", "List Partner Interconnect Attachments", "Retrieves a list of the Partner Interconnect Attachments on your account, including the following information for each:"+interconnectAttachmentDetails, Writer, + aliasOpt("ls"), displayerType(&displayers.PartnerInterconnectAttachment{})) + cmdPartnerIAList.Example = `The following example lists the Partner Interconnect Attachments on your account : doctl partner interconnect-attachment list --format Name,VPCIDs` + + return cmd +} + +// RunPartnerInterconnectAttachmentGet retrieves an existing Partner Interconnect Attachment by its identifier. +func RunPartnerInterconnectAttachmentGet(c *CmdConfig) error { + err := ensureOneArg(c) + if err != nil { + return err + } + iaID := c.Args[0] + + interconnectAttachment, err := c.VPCs().GetPartnerInterconnectAttachment(iaID) + if err != nil { + return err + } + + item := &displayers.PartnerInterconnectAttachment{ + PartnerInterconnectAttachments: do.PartnerInterconnectAttachments{*interconnectAttachment}, + } + return c.Display(item) +} + +// RunPartnerInterconnectAttachmentList lists Partner Interconnect Attachment +func RunPartnerInterconnectAttachmentList(c *CmdConfig) error { + + list, err := c.VPCs().ListPartnerInterconnectAttachments() + if err != nil { + return err + } + + item := &displayers.PartnerInterconnectAttachment{PartnerInterconnectAttachments: list} + return c.Display(item) +} diff --git a/commands/partner_interconnect_attachments_test.go b/commands/partner_interconnect_attachments_test.go new file mode 100644 index 000000000..41f23c9d0 --- /dev/null +++ b/commands/partner_interconnect_attachments_test.go @@ -0,0 +1,57 @@ +package commands + +import ( + "testing" + + "github.com/digitalocean/godo" + "github.com/stretchr/testify/assert" + + "github.com/digitalocean/doctl/do" +) + +var ( + testIA = do.PartnerInterconnectAttachment{ + PartnerInterconnectAttachment: &godo.PartnerInterconnectAttachment{ + Name: "ia-name", + VPCIDs: []string{"f81d4fae-7dec-11d0-a765-00a0c91e6bf6", "3f900b61-30d7-40d8-9711-8c5d6264b268"}, + }, + } + + testIAList = do.PartnerInterconnectAttachments{ + testIA, + } +) + +func TestInterconnectAttachmentsCommand(t *testing.T) { + cmd := InterconnectAttachments() + assert.NotNil(t, cmd) + assertCommandNames(t, cmd, "get", "list") +} + +func TestInterconnectAttachmentsGet(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + iaID := "e819b321-a9a1-4078-b437-8e6b8bf13530" + tm.vpcs.EXPECT().GetPartnerInterconnectAttachment(iaID).Return(&testIA, nil) + + config.Args = append(config.Args, iaID) + + err := RunPartnerInterconnectAttachmentGet(config) + assert.NoError(t, err) + }) +} + +func TestInterconnectAttachmentsGetNoID(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + err := RunPartnerInterconnectAttachmentGet(config) + assert.Error(t, err) + }) +} + +func TestInterconnectAttachmentsList(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + tm.vpcs.EXPECT().ListPartnerInterconnectAttachments().Return(testIAList, nil) + + err := RunPartnerInterconnectAttachmentList(config) + assert.NoError(t, err) + }) +} diff --git a/do/mocks/VPCsService.go b/do/mocks/VPCsService.go index a4f4873f7..b99484b4a 100644 --- a/do/mocks/VPCsService.go +++ b/do/mocks/VPCsService.go @@ -114,6 +114,21 @@ func (mr *MockVPCsServiceMockRecorder) Get(vpcUUID any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockVPCsService)(nil).Get), vpcUUID) } +// GetPartnerInterconnectAttachment mocks base method. +func (m *MockVPCsService) GetPartnerInterconnectAttachment(iaID string) (*do.PartnerInterconnectAttachment, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPartnerInterconnectAttachment", iaID) + ret0, _ := ret[0].(*do.PartnerInterconnectAttachment) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetPartnerInterconnectAttachment indicates an expected call of GetPartnerInterconnectAttachment. +func (mr *MockVPCsServiceMockRecorder) GetPartnerInterconnectAttachment(iaID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPartnerInterconnectAttachment", reflect.TypeOf((*MockVPCsService)(nil).GetPartnerInterconnectAttachment), iaID) +} + // GetPeering mocks base method. func (m *MockVPCsService) GetPeering(peeringID string) (*do.VPCPeering, error) { m.ctrl.T.Helper() @@ -144,6 +159,21 @@ func (mr *MockVPCsServiceMockRecorder) List() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockVPCsService)(nil).List)) } +// ListPartnerInterconnectAttachments mocks base method. +func (m *MockVPCsService) ListPartnerInterconnectAttachments() (do.PartnerInterconnectAttachments, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListPartnerInterconnectAttachments") + ret0, _ := ret[0].(do.PartnerInterconnectAttachments) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListPartnerInterconnectAttachments indicates an expected call of ListPartnerInterconnectAttachments. +func (mr *MockVPCsServiceMockRecorder) ListPartnerInterconnectAttachments() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListPartnerInterconnectAttachments", reflect.TypeOf((*MockVPCsService)(nil).ListPartnerInterconnectAttachments)) +} + // ListVPCPeerings mocks base method. func (m *MockVPCsService) ListVPCPeerings() (do.VPCPeerings, error) { m.ctrl.T.Helper() diff --git a/do/vpcs.go b/do/vpcs.go index fb4f46447..a483a2424 100644 --- a/do/vpcs.go +++ b/do/vpcs.go @@ -35,6 +35,14 @@ type VPCPeering struct { // VPCPeerings is a slice of VPCPeering type VPCPeerings []VPCPeering +// PartnerInterconnectAttachment wraps a godo PartnerInterconnectAttachment +type PartnerInterconnectAttachment struct { + *godo.PartnerInterconnectAttachment +} + +// PartnerInterconnectAttachments is a slice of PartnerInterconnectAttachment +type PartnerInterconnectAttachments []PartnerInterconnectAttachment + // VPCsService is the godo VPCsService interface. type VPCsService interface { Get(vpcUUID string) (*VPC, error) @@ -49,6 +57,8 @@ type VPCsService interface { UpdateVPCPeering(peeringID string, req *godo.VPCPeeringUpdateRequest) (*VPCPeering, error) DeleteVPCPeering(peeringID string) error ListVPCPeeringsByVPCID(vpcID string) (VPCPeerings, error) + GetPartnerInterconnectAttachment(iaID string) (*PartnerInterconnectAttachment, error) + ListPartnerInterconnectAttachments() (PartnerInterconnectAttachments, error) } var _ VPCsService = &vpcsService{} @@ -221,3 +231,40 @@ func (v *vpcsService) ListVPCPeeringsByVPCID(vpcID string) (VPCPeerings, error) return list, nil } + +func (v *vpcsService) GetPartnerInterconnectAttachment(iaID string) (*PartnerInterconnectAttachment, error) { + partnerIA, _, err := v.client.PartnerInterconnectAttachments.Get(context.TODO(), iaID) + if err != nil { + return nil, err + } + return &PartnerInterconnectAttachment{PartnerInterconnectAttachment: partnerIA}, nil +} + +func (v *vpcsService) ListPartnerInterconnectAttachments() (PartnerInterconnectAttachments, error) { + f := func(opt *godo.ListOptions) ([]any, *godo.Response, error) { + list, resp, err := v.client.PartnerInterconnectAttachments.List(context.TODO(), opt) + if err != nil { + return nil, nil, err + } + + si := make([]any, len(list)) + for i := range list { + si[i] = list[i] + } + + return si, resp, err + } + + si, err := PaginateResp(f) + if err != nil { + return nil, err + } + + list := make([]PartnerInterconnectAttachment, len(si)) + for i := range si { + a := si[i].(*godo.PartnerInterconnectAttachment) + list[i] = PartnerInterconnectAttachment{PartnerInterconnectAttachment: a} + } + + return list, nil +} diff --git a/vendor/github.com/digitalocean/godo/util/droplet.go b/vendor/github.com/digitalocean/godo/util/droplet.go index 80abd987f..e4f62062b 100644 --- a/vendor/github.com/digitalocean/godo/util/droplet.go +++ b/vendor/github.com/digitalocean/godo/util/droplet.go @@ -4,8 +4,8 @@ import ( "context" "fmt" "time" - - "github.internal.digitalocean.com/digitalocean/godo" + + "github.com/digitalocean/godo" ) const (