Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

doctl: get, list, update and delete interconnect attachment #1636

Merged
merged 14 commits into from
Feb 10, 2025
224 changes: 222 additions & 2 deletions commands/partner_interconnect_attachment.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@ package commands

import (
"fmt"
"os"
"strings"
"time"

"github.com/digitalocean/godo"
"github.com/spf13/cobra"

"github.com/digitalocean/doctl"
"github.com/digitalocean/doctl/commands/displayers"
"github.com/digitalocean/doctl/do"
"github.com/digitalocean/godo"
"github.com/spf13/cobra"
)

// Network creates the network command.
Expand Down Expand Up @@ -67,6 +71,56 @@ With the Partner Interconnect Attachments commands, you can get, list, create, u
AddStringFlag(cmdPartnerIACreate, doctl.ArgPartnerInterconnectAttachmentBGPPeerRouterIP, "", "", "BGP Peer Router IP")
cmdPartnerIACreate.Example = `The following example creates a Partner Interconnect Attachment: doctl network interconnect-attachment create --name "example-pia" --connection-bandwidth-in-mbps 50 --naas-provider "MEGAPORT" --region "nyc" --vpc-ids "c5537207-ebf0-47cb-bc10-6fac717cd672"`

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 <interconnect-attachment-id>",
"Retrieves a Partner Interconnect Attachment", "Retrieves information about a Partner Interconnect Attachment, including:"+interconnectAttachmentDetails, Writer,
aliasOpt("g"), displayerType(&displayers.PartnerInterconnectAttachment{}))
AddStringFlag(cmdPartnerIAGet, doctl.ArgInterconnectAttachmentType, "", "partner", "Specify interconnect attachment type (e.g., partner)")
cmdPartnerIAGet.Example = `The following example retrieves information about a Partner Interconnect Attachment with the ID ` + "`" + `f81d4fae-7dec-11d0-a765-00a0c91e6bf6` + "`" +
`: doctl network --type "partner" interconnect-attachment get f81d4fae-7dec-11d0-a765-00a0c91e6bf6`

cmdPartnerIAList := CmdBuilder(cmd, RunPartnerInterconnectAttachmentList, "list", "List Network Interconnect Attachments", "Retrieves a list of the Network Interconnect Attachments on your account, including the following information for each:"+interconnectAttachmentDetails, Writer,
aliasOpt("ls"), displayerType(&displayers.PartnerInterconnectAttachment{}))
AddStringFlag(cmdPartnerIAList, doctl.ArgInterconnectAttachmentType, "", "partner", "Specify interconnect attachment type (e.g., partner)")
cmdPartnerIAList.Example = `The following example lists the Network Interconnect Attachments on your account :` +
` doctl network --type "partner" interconnect-attachment list --format Name,VPCIDs `

cmdPartnerIADelete := CmdBuilder(cmd, RunPartnerInterconnectAttachmentDelete, "delete <interconnect-attachment-id>",
"Deletes a Partner Interconnect Attachment", "Deletes information about a Partner Interconnect Attachment. This is irreversible ", Writer,
aliasOpt("rm"), displayerType(&displayers.PartnerInterconnectAttachment{}))
AddBoolFlag(cmdPartnerIADelete, doctl.ArgForce, doctl.ArgShortForce, false,
"Delete the VPC Peering without any confirmation prompt")
AddBoolFlag(cmdPartnerIADelete, doctl.ArgCommandWait, "", false,
"Boolean that specifies whether to wait for a VPC Peering deletion to complete before returning control to the terminal")
AddStringFlag(cmdPartnerIADelete, doctl.ArgInterconnectAttachmentType, "", "partner", "Specify interconnect attachment type (e.g., partner)")
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 <interconnect-attachment-id>",
"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.ArgInterconnectAttachmentType, "", "partner", "Specify interconnect attachment type (e.g., partner)")
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" --
vpc-ids "270a76ed-1bb7-4c5d-a6a5-e863de086940"`

return cmd
}

Expand Down Expand Up @@ -153,3 +207,169 @@ func RunPartnerInterconnectAttachmentCreate(c *CmdConfig) error {
item := &displayers.PartnerInterconnectAttachment{PartnerInterconnectAttachments: do.PartnerInterconnectAttachments{*pia}}
return c.Display(item)
}

// RunPartnerInterconnectAttachmentGet retrieves an existing Partner Interconnect Attachment by its identifier.
func RunPartnerInterconnectAttachmentGet(c *CmdConfig) error {

if err := ensurePartnerAttachmentType(c); err != nil {
return err
}

err := ensureOneArg(c)
if err != nil {
return err
}
iaID := c.Args[0]

pias := c.PartnerInterconnectAttachments()
interconnectAttachment, err := pias.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 {

if err := ensurePartnerAttachmentType(c); err != nil {
return err
}

pias := c.PartnerInterconnectAttachments()
list, err := pias.ListPartnerInterconnectAttachments()
if err != nil {
return err
}

item := &displayers.PartnerInterconnectAttachment{PartnerInterconnectAttachments: list}
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.PartnerInterconnectAttachments().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 {

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 {

pias := c.PartnerInterconnectAttachments()
err := pias.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(pias, 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(pias do.PartnerInterconnectAttachmentsService, 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 := pias.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)
}
84 changes: 83 additions & 1 deletion commands/partner_interconnect_attachment_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package commands

import (
"strings"
"testing"
"time"

Expand All @@ -23,13 +24,17 @@ var (
CreatedAt: time.Date(2025, 1, 30, 0, 0, 0, 0, time.UTC),
},
}

testPartnerIAList = do.PartnerInterconnectAttachments{
testPartnerAttachment,
}
)

func TestPartnerInterconnectAttachmentsCommand(t *testing.T) {
cmd := PartnerInterconnectAttachments()
assert.NotNil(t, cmd)

assertCommandNames(t, cmd, "create")
assertCommandNames(t, cmd, "create", "get", "list", "delete", "update")
}

func TestPartnerInterconnectAttachmentCreate(t *testing.T) {
Expand Down Expand Up @@ -68,3 +73,80 @@ func TestPartnerInterconnectAttachmentCreateUnsupportedType(t *testing.T) {
assert.Contains(t, err.Error(), "unsupported attachment type")
})
}

func TestInterconnectAttachmentsGet(t *testing.T) {
withTestClient(t, func(config *CmdConfig, tm *tcMocks) {
config.Doit.Set(config.NS, doctl.ArgInterconnectAttachmentType, "partner")

iaID := "e819b321-a9a1-4078-b437-8e6b8bf13530"
tm.partnerInterconnectAttachment.EXPECT().GetPartnerInterconnectAttachment(iaID).Return(&testPartnerAttachment, 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) {
config.Doit.Set(config.NS, doctl.ArgInterconnectAttachmentType, "partner")

err := RunPartnerInterconnectAttachmentGet(config)
assert.Error(t, err)
})
}

func TestInterconnectAttachmentsList(t *testing.T) {
withTestClient(t, func(config *CmdConfig, tm *tcMocks) {
config.Doit.Set(config.NS, doctl.ArgInterconnectAttachmentType, "partner")

tm.partnerInterconnectAttachment.EXPECT().ListPartnerInterconnectAttachments().Return(testPartnerIAList, nil)

err := RunPartnerInterconnectAttachmentList(config)
assert.NoError(t, err)
})
}

func TestInterconnectAttachmentsDelete(t *testing.T) {
withTestClient(t, func(config *CmdConfig, tm *tcMocks) {
config.Doit.Set(config.NS, doctl.ArgInterconnectAttachmentType, "partner")

iaID := "e819b321-a9a1-4078-b437-8e6b8bf13530"
tm.partnerInterconnectAttachment.EXPECT().DeletePartnerInterconnectAttachment(iaID).Return(nil)

config.Args = append(config.Args, iaID)
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(config.NS, 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.partnerInterconnectAttachment.EXPECT().UpdatePartnerInterconnectAttachment(iaID, &r).Return(&testPartnerAttachment, 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(config.NS, doctl.ArgInterconnectAttachmentType, "partner")

err := RunPartnerInterconnectAttachmentUpdate(config)
assert.Error(t, err)
})
}
Loading
Loading