diff --git a/args.go b/args.go index d6d4ab43b..9b2b381ad 100644 --- a/args.go +++ b/args.go @@ -468,6 +468,13 @@ const ( // ArgVPCIPRange is a VPC range of IP addresses in CIDR notation. ArgVPCIPRange = "ip-range" + // ArgVPCPeeringName is a name of the VPC Peering. + ArgVPCPeeringName = "name" + // ArgVPCPeeringVPCIDs is the vpc ids of the peering + ArgVPCPeeringVPCIDs = "vpc-ids" + // ArgVPCPeeringVPCID is id of the VPC. + ArgVPCPeeringVPCID = "vpc-id" + // ArgReadWrite indicates a generated token should be read/write. ArgReadWrite = "read-write" // ArgRegistry indicates the name of the registry. diff --git a/commands/displayers/vpc_peering.go b/commands/displayers/vpc_peering.go new file mode 100644 index 000000000..26ee38f78 --- /dev/null +++ b/commands/displayers/vpc_peering.go @@ -0,0 +1,54 @@ +package displayers + +import ( + "github.com/digitalocean/doctl/do" + "io" + "strings" +) + +type VPCPeering struct { + VPCPeerings do.VPCPeerings +} + +var _ Displayable = &VPCPeering{} + +func (v *VPCPeering) JSON(out io.Writer) error { + return writeJSON(v.VPCPeerings, out) +} + +func (v *VPCPeering) Cols() []string { + return []string{ + "ID", + "Name", + "VPCIDs", + "Status", + "Created", + } +} + +func (v *VPCPeering) ColMap() map[string]string { + return map[string]string{ + "ID": "ID", + "Name": "Name", + "VPCIDs": "VPCIDs", + "Status": "Status", + "Created": "Created At", + } +} + +func (v *VPCPeering) KV() []map[string]any { + out := make([]map[string]any, 0, len(v.VPCPeerings)) + + for _, v := range v.VPCPeerings { + o := map[string]any{ + "ID": v.ID, + "Name": v.Name, + "VPCIDs": strings.Join(v.VPCIDs, ","), + "Status": v.Status, + "Created": v.CreatedAt, + } + out = append(out, o) + } + + return out +} diff --git a/commands/vpc_peerings.go b/commands/vpc_peerings.go new file mode 100644 index 000000000..f1e0f11cf --- /dev/null +++ b/commands/vpc_peerings.go @@ -0,0 +1,270 @@ +package commands + +import ( + "errors" + "fmt" + "os" + "strings" + "time" + + "github.com/digitalocean/doctl" + "github.com/digitalocean/doctl/commands/displayers" + "github.com/digitalocean/doctl/do" + "github.com/digitalocean/godo" + "github.com/spf13/cobra" +) + +// VPCPeerings creates the vpc peerings command. +func VPCPeerings() *Command { + cmd := &Command{ + Command: &cobra.Command{ + Use: "peerings", + Short: "Display commands that manage VPC Peerings", + Long: `The commands under ` + "`" + `doctl vpcs peerings` + "`" + ` are for managing your VPC Peerings. +With the VPC Peerings commands, you can get, list, create, update, or delete VPC Peerings, and manage their configuration details.`, + Hidden: true, + }, + } + + peeringDetails := ` +- The VPC Peering ID +- The VPC Peering Name +- The Peered VPC network IDs +- The VPC Peering Status +- The VPC Peering creation date, in ISO8601 combined date and time format +` + cmdPeeringGet := CmdBuilder(cmd, RunVPCPeeringGet, "get ", + "Retrieves a VPC Peering", "Retrieves information about a VPC Peering, including:"+peeringDetails, Writer, + aliasOpt("g"), displayerType(&displayers.VPCPeering{})) + cmdPeeringGet.Example = `The following example retrieves information about a VPC Peering with the ID ` + "`" + `f81d4fae-7dec-11d0-a765-00a0c91e6bf6` + "`" + `: doctl vpcs peerings get f81d4fae-7dec-11d0-a765-00a0c91e6bf6` + + cmdPeeringList := CmdBuilder(cmd, RunVPCPeeringList, "list", "List VPC Peerings", "Retrieves a list of the VPC Peerings on your account, including the following information for each:"+peeringDetails, Writer, + aliasOpt("ls"), displayerType(&displayers.VPCPeering{})) + AddStringFlag(cmdPeeringList, doctl.ArgVPCPeeringVPCID, "", "", + "VPC ID") + cmdPeeringList.Example = `The following example lists the VPC Peerings on your account : doctl vpcs peerings list --format Name,VPCIDs` + + cmdPeeringCreate := CmdBuilder(cmd, RunVPCPeeringCreate, "create", + "Create a new VPC Peering", "Use this command to create a new VPC Peering on your account.", Writer, aliasOpt("c")) + AddStringFlag(cmdPeeringCreate, doctl.ArgVPCPeeringVPCIDs, "", "", + "Peering VPC IDs should be comma separated", requiredOpt()) + AddBoolFlag(cmdPeeringCreate, doctl.ArgCommandWait, "", false, "Boolean that specifies whether to wait for a VPC Peering creation to complete before returning control to the terminal") + cmdPeeringCreate.Example = `The following example creates a VPC Peering named ` + + "`" + `example-peering-name` + "`" + + ` : doctl vpcs peerings create example-peering-name --vpc-ids f81d4fae-7dec-11d0-a765-00a0c91e6bf6,3f900b61-30d7-40d8-9711-8c5d6264b268` + + cmdPeeringUpdate := CmdBuilder(cmd, RunVPCPeeringUpdate, "update ", + "Update a VPC Peering's name", `Use this command to update the name of a VPC Peering`, Writer, aliasOpt("u")) + AddStringFlag(cmdPeeringUpdate, doctl.ArgVPCPeeringName, "", "", + "The VPC Peering's name", requiredOpt()) + cmdPeeringUpdate.Example = `The following example updates the name of a VPC Peering with the ID ` + + "`" + `f81d4fae-7dec-11d0-a765-00a0c91e6bf6` + "`" + ` to ` + "`" + `new-name` + "`" + + `: doctl vpcs peerings update f81d4fae-7dec-11d0-a765-00a0c91e6bf6 --name new-name` + + cmdPeeringDelete := CmdBuilder(cmd, RunVPCPeeringDelete, "delete ", + "Permanently delete a VPC Peering", `Permanently deletes the specified VPC Peering. This is irreversible.`, Writer, aliasOpt("d", "rm")) + AddBoolFlag(cmdPeeringDelete, doctl.ArgForce, doctl.ArgShortForce, false, + "Delete the VPC Peering without any confirmation prompt") + AddBoolFlag(cmdPeeringDelete, doctl.ArgCommandWait, "", false, + "Boolean that specifies whether to wait for a VPC Peering deletion to complete before returning control to the terminal") + cmdPeeringDelete.Example = `The following example deletes the VPC Peering with the ID ` + "`" + `f81d4fae-7dec-11d0-a765-00a0c91e6bf6` + "`" + + `: doctl vpcs peerings delete f81d4fae-7dec-11d0-a765-00a0c91e6bf6` + + return cmd +} + +// RunVPCPeeringGet retrieves an existing VPC Peering by its identifier. +func RunVPCPeeringGet(c *CmdConfig) error { + err := ensureOneArg(c) + if err != nil { + return err + } + peeringID := c.Args[0] + + peering, err := c.VPCs().GetPeering(peeringID) + if err != nil { + return err + } + + item := &displayers.VPCPeering{VPCPeerings: do.VPCPeerings{*peering}} + return c.Display(item) +} + +// RunVPCPeeringCreate creates a new VPC Peering with a given configuration. +func RunVPCPeeringCreate(c *CmdConfig) error { + if len(c.Args) == 0 { + return doctl.NewMissingArgsErr(c.NS) + } + peeringName := c.Args[0] + + r := new(godo.VPCPeeringCreateRequest) + r.Name = peeringName + + vpcIDs, err := c.Doit.GetString(c.NS, doctl.ArgVPCPeeringVPCIDs) + if err != nil { + return err + } + + for _, v := range strings.Split(vpcIDs, ",") { + if v == "" { + return errors.New("VPC ID is empty") + } + + r.VPCIDs = append(r.VPCIDs, strings.TrimSpace(v)) + } + + if len(r.VPCIDs) != 2 { + return errors.New("VPC IDs length should be 2") + } + + vpcService := c.VPCs() + peering, err := vpcService.CreateVPCPeering(r) + if err != nil { + return err + } + + wait, err := c.Doit.GetBool(c.NS, doctl.ArgCommandWait) + if err != nil { + return err + } + + if wait { + notice("VPC Peering creation is in progress, waiting for VPC Peering to become active") + + err := waitForVPCPeering(vpcService, peering.ID, "ACTIVE", false) + if err != nil { + return fmt.Errorf("VPC Peering couldn't enter `active` state: %v", err) + } + + peering, _ = vpcService.GetPeering(peering.ID) + } + + item := &displayers.VPCPeering{VPCPeerings: do.VPCPeerings{*peering}} + return c.Display(item) +} + +// RunVPCPeeringList lists VPC Peerings +func RunVPCPeeringList(c *CmdConfig) error { + vpcID, err := c.Doit.GetString(c.NS, doctl.ArgVPCPeeringVPCID) + if err != nil { + return err + } + + var list do.VPCPeerings + if vpcID == "" { + list, err = c.VPCs().ListVPCPeerings() + if err != nil { + return err + } + } else { + list, err = c.VPCs().ListVPCPeeringsByVPCID(vpcID) + if err != nil { + return err + } + } + + item := &displayers.VPCPeering{VPCPeerings: list} + return c.Display(item) +} + +// RunVPCPeeringUpdate updates an existing VPC Peering with new configuration. +func RunVPCPeeringUpdate(c *CmdConfig) error { + err := ensureOneArg(c) + if err != nil { + return err + } + peeringID := c.Args[0] + + r := new(godo.VPCPeeringUpdateRequest) + name, err := c.Doit.GetString(c.NS, doctl.ArgVPCPeeringName) + if err != nil { + return err + } + r.Name = name + + peering, err := c.VPCs().UpdateVPCPeering(peeringID, r) + if err != nil { + return err + } + + item := &displayers.VPCPeering{VPCPeerings: do.VPCPeerings{*peering}} + return c.Display(item) +} + +// RunVPCPeeringDelete deletes a VPC Peering by its identifier. +func RunVPCPeeringDelete(c *CmdConfig) error { + if len(c.Args) == 0 { + return doctl.NewMissingArgsErr(c.NS) + } + + peeringID := c.Args[0] + + force, err := c.Doit.GetBool(c.NS, doctl.ArgForce) + if err != nil { + return err + } + + if force || AskForConfirmDelete("VPC Peering", 1) == nil { + vpcs := c.VPCs() + if err := vpcs.DeleteVPCPeering(peeringID); err != nil { + return err + } + + wait, err := c.Doit.GetBool(c.NS, doctl.ArgCommandWait) + if err != nil { + return err + } + + if wait { + notice("VPC Peering deletion is in progress, waiting for VPC Peering to be deleted") + + err := waitForVPCPeering(vpcs, peeringID, "DELETED", true) + if err != nil { + return fmt.Errorf("VPC Peering couldn't be deleted : %v", err) + } + } + + } else { + return fmt.Errorf("operation aborted") + } + notice("VPC Peering is successfully deleted") + return nil +} + +func waitForVPCPeering(vpcService do.VPCsService, peeringID 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) + } + } + + peering, err := vpcService.GetPeering(peeringID) + if err != nil { + if terminateOnNotFound && strings.Contains(err.Error(), "not found") { + return nil + } + return err + } + + if peering.Status == errStatus { + return fmt.Errorf("VPC Peering (%s) entered status `%s`", peeringID, errStatus) + } + + if peering.Status == wantStatus { + return nil + } + + attempts++ + time.Sleep(5 * time.Second) + } + + return fmt.Errorf("timeout waiting for VPC Peering (%s) to become %s", peeringID, wantStatus) +} diff --git a/commands/vpc_peerings_test.go b/commands/vpc_peerings_test.go new file mode 100644 index 000000000..2c5e549a6 --- /dev/null +++ b/commands/vpc_peerings_test.go @@ -0,0 +1,142 @@ +package commands + +import ( + "github.com/digitalocean/doctl" + "testing" + + "github.com/digitalocean/doctl/do" + "github.com/digitalocean/godo" + "github.com/stretchr/testify/assert" +) + +var ( + testPeering = do.VPCPeering{ + VPCPeering: &godo.VPCPeering{ + Name: "peering-name", + VPCIDs: []string{"f81d4fae-7dec-11d0-a765-00a0c91e6bf6", "3f900b61-30d7-40d8-9711-8c5d6264b268"}, + Status: "ACTIVE", + }, + } + + testPeeringList = do.VPCPeerings{ + testPeering, + } +) + +func TestVPCPeeringsCommand(t *testing.T) { + cmd := VPCPeerings() + assert.NotNil(t, cmd) + assertCommandNames(t, cmd, "get", "list", "create", "update", "delete") +} + +func TestVPCPeeringGet(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + peeringID := "e819b321-a9a1-4078-b437-8e6b8bf13530" + tm.vpcs.EXPECT().GetPeering(peeringID).Return(&testPeering, nil) + + config.Args = append(config.Args, peeringID) + + err := RunVPCPeeringGet(config) + assert.NoError(t, err) + }) +} + +func TestVPCPeeringGetNoID(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + err := RunVPCPeeringGet(config) + assert.Error(t, err) + }) +} + +func TestVPCPeeringList(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + tm.vpcs.EXPECT().ListVPCPeerings().Return(testPeeringList, nil) + + err := RunVPCPeeringList(config) + assert.NoError(t, err) + }) +} + +func TestVPCPeeringListByVpcID(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + vpcID := "vpc-01" + tm.vpcs.EXPECT().ListVPCPeeringsByVPCID(vpcID).Return(testPeeringList, nil) + + config.Doit.Set(config.NS, doctl.ArgVPCPeeringVPCID, vpcID) + err := RunVPCPeeringList(config) + assert.NoError(t, err) + }) +} + +func TestVPCPeeringCreate(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + r := godo.VPCPeeringCreateRequest{ + Name: "peering-name", + VPCIDs: []string{"f81d4fae-7dec-11d0-a765-00a0c91e6bf6", "3f900b61-30d7-40d8-9711-8c5d6264b268"}, + } + tm.vpcs.EXPECT().CreateVPCPeering(&r).Return(&testPeering, nil) + + config.Args = append(config.Args, "peering-name") + config.Doit.Set(config.NS, doctl.ArgVPCPeeringVPCIDs, "f81d4fae-7dec-11d0-a765-00a0c91e6bf6,3f900b61-30d7-40d8-9711-8c5d6264b268") + + err := RunVPCPeeringCreate(config) + assert.NoError(t, err) + }) + + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + config.Args = append(config.Args, "peering-name") + + err := RunVPCPeeringCreate(config) + assert.EqualError(t, err, "VPC ID is empty") + }) + + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + config.Args = append(config.Args, "peering-name") + config.Doit.Set(config.NS, doctl.ArgVPCPeeringVPCIDs, "f81d4fae-7dec-11d0-a765-00a0c91e6bf6") + + err := RunVPCPeeringCreate(config) + assert.EqualError(t, err, "VPC IDs length should be 2") + }) +} + +func TestVPCPeeringUpdate(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + peeringID := "peering-uuid1" + peeringName := "updated-peering-name" + r := godo.VPCPeeringUpdateRequest{Name: peeringName} + tm.vpcs.EXPECT().UpdateVPCPeering(peeringID, &r).Return(&testPeering, nil) + + config.Args = append(config.Args, peeringID) + config.Doit.Set(config.NS, doctl.ArgVPCPeeringName, "updated-peering-name") + + err := RunVPCPeeringUpdate(config) + assert.NoError(t, err) + }) +} + +func TestVPCPeeringUpdateNoID(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + err := RunVPCPeeringUpdate(config) + assert.Error(t, err) + }) +} + +func TestVPCPeeringDelete(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + peeringID := "e819b321-a9a1-4078-b437-8e6b8bf13530" + tm.vpcs.EXPECT().DeleteVPCPeering(peeringID).Return(nil) + + config.Args = append(config.Args, peeringID) + config.Doit.Set(config.NS, doctl.ArgForce, true) + + err := RunVPCPeeringDelete(config) + assert.NoError(t, err) + }) +} + +func TestVPCPeeringDeleteNoID(t *testing.T) { + withTestClient(t, func(config *CmdConfig, tm *tcMocks) { + err := RunVPCPeeringDelete(config) + assert.Error(t, err) + }) +} diff --git a/commands/vpcs.go b/commands/vpcs.go index 64e8f787f..a3895bed5 100644 --- a/commands/vpcs.go +++ b/commands/vpcs.go @@ -37,6 +37,8 @@ With the VPC commands, you can list, create, or delete VPCs, and manage their co }, } + cmd.AddCommand(VPCPeerings()) + vpcDetail := ` - The VPC network's ID diff --git a/commands/vpcs_test.go b/commands/vpcs_test.go index 5d5660c25..9c0ed424a 100644 --- a/commands/vpcs_test.go +++ b/commands/vpcs_test.go @@ -27,7 +27,7 @@ var ( func TestVPCsCommand(t *testing.T) { cmd := VPCs() assert.NotNil(t, cmd) - assertCommandNames(t, cmd, "get", "list", "create", "update", "delete") + assertCommandNames(t, cmd, "get", "list", "create", "update", "delete", "peerings") } func TestVPCGet(t *testing.T) { diff --git a/do/mocks/VPCsService.go b/do/mocks/VPCsService.go index 6074c3db2..33d86691f 100644 --- a/do/mocks/VPCsService.go +++ b/do/mocks/VPCsService.go @@ -55,6 +55,21 @@ func (mr *MockVPCsServiceMockRecorder) Create(vpcr any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockVPCsService)(nil).Create), vpcr) } +// CreateVPCPeering mocks base method. +func (m *MockVPCsService) CreateVPCPeering(req *godo.VPCPeeringCreateRequest) (*do.VPCPeering, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateVPCPeering", req) + ret0, _ := ret[0].(*do.VPCPeering) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateVPCPeering indicates an expected call of CreateVPCPeering. +func (mr *MockVPCsServiceMockRecorder) CreateVPCPeering(req any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateVPCPeering", reflect.TypeOf((*MockVPCsService)(nil).CreateVPCPeering), req) +} + // Delete mocks base method. func (m *MockVPCsService) Delete(vpcUUID string) error { m.ctrl.T.Helper() @@ -69,6 +84,20 @@ func (mr *MockVPCsServiceMockRecorder) Delete(vpcUUID any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockVPCsService)(nil).Delete), vpcUUID) } +// DeleteVPCPeering mocks base method. +func (m *MockVPCsService) DeleteVPCPeering(peeringID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteVPCPeering", peeringID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteVPCPeering indicates an expected call of DeleteVPCPeering. +func (mr *MockVPCsServiceMockRecorder) DeleteVPCPeering(peeringID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteVPCPeering", reflect.TypeOf((*MockVPCsService)(nil).DeleteVPCPeering), peeringID) +} + // Get mocks base method. func (m *MockVPCsService) Get(vpcUUID string) (*do.VPC, error) { m.ctrl.T.Helper() @@ -84,6 +113,21 @@ func (mr *MockVPCsServiceMockRecorder) Get(vpcUUID any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockVPCsService)(nil).Get), vpcUUID) } +// GetPeering mocks base method. +func (m *MockVPCsService) GetPeering(peeringID string) (*do.VPCPeering, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPeering", peeringID) + ret0, _ := ret[0].(*do.VPCPeering) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetPeering indicates an expected call of GetPeering. +func (mr *MockVPCsServiceMockRecorder) GetPeering(peeringID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPeering", reflect.TypeOf((*MockVPCsService)(nil).GetPeering), peeringID) +} + // List mocks base method. func (m *MockVPCsService) List() (do.VPCs, error) { m.ctrl.T.Helper() @@ -99,6 +143,36 @@ func (mr *MockVPCsServiceMockRecorder) List() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockVPCsService)(nil).List)) } +// ListVPCPeerings mocks base method. +func (m *MockVPCsService) ListVPCPeerings() (do.VPCPeerings, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListVPCPeerings") + ret0, _ := ret[0].(do.VPCPeerings) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListVPCPeerings indicates an expected call of ListVPCPeerings. +func (mr *MockVPCsServiceMockRecorder) ListVPCPeerings() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListVPCPeerings", reflect.TypeOf((*MockVPCsService)(nil).ListVPCPeerings)) +} + +// ListVPCPeeringsByVPCID mocks base method. +func (m *MockVPCsService) ListVPCPeeringsByVPCID(vpcID string) (do.VPCPeerings, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListVPCPeeringsByVPCID", vpcID) + ret0, _ := ret[0].(do.VPCPeerings) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListVPCPeeringsByVPCID indicates an expected call of ListVPCPeeringsByVPCID. +func (mr *MockVPCsServiceMockRecorder) ListVPCPeeringsByVPCID(vpcID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListVPCPeeringsByVPCID", reflect.TypeOf((*MockVPCsService)(nil).ListVPCPeeringsByVPCID), vpcID) +} + // PartialUpdate mocks base method. func (m *MockVPCsService) PartialUpdate(vpcUUID string, options ...godo.VPCSetField) (*do.VPC, error) { m.ctrl.T.Helper() @@ -133,3 +207,18 @@ func (mr *MockVPCsServiceMockRecorder) Update(vpcUUID, vpcr any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockVPCsService)(nil).Update), vpcUUID, vpcr) } + +// UpdateVPCPeering mocks base method. +func (m *MockVPCsService) UpdateVPCPeering(peeringID string, req *godo.VPCPeeringUpdateRequest) (*do.VPCPeering, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateVPCPeering", peeringID, req) + ret0, _ := ret[0].(*do.VPCPeering) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateVPCPeering indicates an expected call of UpdateVPCPeering. +func (mr *MockVPCsServiceMockRecorder) UpdateVPCPeering(peeringID, req any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateVPCPeering", reflect.TypeOf((*MockVPCsService)(nil).UpdateVPCPeering), peeringID, req) +} diff --git a/do/vpcs.go b/do/vpcs.go index be85bb033..fb4f46447 100644 --- a/do/vpcs.go +++ b/do/vpcs.go @@ -27,6 +27,14 @@ type VPC struct { // VPCs is a slice of VPC. type VPCs []VPC +// VPCPeering wraps a godo VPCPeering +type VPCPeering struct { + *godo.VPCPeering +} + +// VPCPeerings is a slice of VPCPeering +type VPCPeerings []VPCPeering + // VPCsService is the godo VPCsService interface. type VPCsService interface { Get(vpcUUID string) (*VPC, error) @@ -35,6 +43,12 @@ type VPCsService interface { Update(vpcUUID string, vpcr *godo.VPCUpdateRequest) (*VPC, error) PartialUpdate(vpcUUID string, options ...godo.VPCSetField) (*VPC, error) Delete(vpcUUID string) error + GetPeering(peeringID string) (*VPCPeering, error) + ListVPCPeerings() (VPCPeerings, error) + CreateVPCPeering(req *godo.VPCPeeringCreateRequest) (*VPCPeering, error) + UpdateVPCPeering(peeringID string, req *godo.VPCPeeringUpdateRequest) (*VPCPeering, error) + DeleteVPCPeering(peeringID string) error + ListVPCPeeringsByVPCID(vpcID string) (VPCPeerings, error) } var _ VPCsService = &vpcsService{} @@ -119,3 +133,91 @@ func (v *vpcsService) Delete(vpcUUID string) error { _, err := v.client.VPCs.Delete(context.TODO(), vpcUUID) return err } + +func (v *vpcsService) GetPeering(peeringID string) (*VPCPeering, error) { + peering, _, err := v.client.VPCs.GetVPCPeering(context.TODO(), peeringID) + if err != nil { + return nil, err + } + return &VPCPeering{VPCPeering: peering}, nil +} + +func (v *vpcsService) ListVPCPeerings() (VPCPeerings, error) { + f := func(opt *godo.ListOptions) ([]any, *godo.Response, error) { + list, resp, err := v.client.VPCs.ListVPCPeerings(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([]VPCPeering, len(si)) + for i := range si { + a := si[i].(*godo.VPCPeering) + list[i] = VPCPeering{VPCPeering: a} + } + + return list, nil +} + +func (v *vpcsService) UpdateVPCPeering(peeringID string, req *godo.VPCPeeringUpdateRequest) (*VPCPeering, error) { + peering, _, err := v.client.VPCs.UpdateVPCPeering(context.TODO(), peeringID, req) + if err != nil { + return nil, err + } + + return &VPCPeering{VPCPeering: peering}, nil +} + +func (v *vpcsService) DeleteVPCPeering(peeringID string) error { + _, err := v.client.VPCs.DeleteVPCPeering(context.TODO(), peeringID) + return err +} + +func (v *vpcsService) CreateVPCPeering(req *godo.VPCPeeringCreateRequest) (*VPCPeering, error) { + peering, _, err := v.client.VPCs.CreateVPCPeering(context.TODO(), req) + if err != nil { + return nil, err + } + return &VPCPeering{VPCPeering: peering}, nil +} + +func (v *vpcsService) ListVPCPeeringsByVPCID(vpcID string) (VPCPeerings, error) { + f := func(opt *godo.ListOptions) ([]any, *godo.Response, error) { + list, resp, err := v.client.VPCs.ListVPCPeeringsByVPCID(context.TODO(), vpcID, 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([]VPCPeering, len(si)) + for i := range si { + a := si[i].(*godo.VPCPeering) + list[i] = VPCPeering{VPCPeering: a} + } + + return list, nil +} diff --git a/go.mod b/go.mod index 255ef6723..380485ef5 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.21 require ( github.com/blang/semver v3.5.1+incompatible github.com/creack/pty v1.1.11 - github.com/digitalocean/godo v1.115.0 + github.com/digitalocean/godo v1.116.0 github.com/docker/cli v24.0.5+incompatible github.com/docker/docker v24.0.9+incompatible github.com/docker/docker-credential-helpers v0.7.0 // indirect diff --git a/go.sum b/go.sum index 877a4d1de..05424209d 100644 --- a/go.sum +++ b/go.sum @@ -86,8 +86,10 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/digitalocean/godo v1.115.0 h1:Xv0gwN0t7ldD61QKeYeHHwCEKfTXzAOmnUDgGAzoZw4= -github.com/digitalocean/godo v1.115.0/go.mod h1:Vk0vpCot2HOAJwc5WE8wljZGtJ3ZtWIc8MQ8rF38sdo= +github.com/digitalocean/godo v1.115.1-0.20240515191029-705fb26c5aa5 h1:bAO9uVDeZhwy1DdKmtAqgjS9+isKWnzxzxiKvNHKNtQ= +github.com/digitalocean/godo v1.115.1-0.20240515191029-705fb26c5aa5/go.mod h1:Vk0vpCot2HOAJwc5WE8wljZGtJ3ZtWIc8MQ8rF38sdo= +github.com/digitalocean/godo v1.116.0 h1:SuF/Imd1/dE/nYrUFVkJ2itesQNnJQE1a/vmtHknxeE= +github.com/digitalocean/godo v1.116.0/go.mod h1:Vk0vpCot2HOAJwc5WE8wljZGtJ3ZtWIc8MQ8rF38sdo= github.com/docker/cli v24.0.5+incompatible h1:WeBimjvS0eKdH4Ygx+ihVq1Q++xg36M/rMi4aXAvodc= github.com/docker/cli v24.0.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= diff --git a/vendor/github.com/digitalocean/godo/CHANGELOG.md b/vendor/github.com/digitalocean/godo/CHANGELOG.md index e1fb20fe7..1d3102b2e 100644 --- a/vendor/github.com/digitalocean/godo/CHANGELOG.md +++ b/vendor/github.com/digitalocean/godo/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## [v1.116.0] - 2024-05-16 + +- #693 - @guptado - Introduce VPC peering methods + ## [v1.115.0] - 2024-05-08 - #688 - @asaha2 - load balancers: support glb active-passive fail-over settings, currently in closed beta diff --git a/vendor/github.com/digitalocean/godo/godo.go b/vendor/github.com/digitalocean/godo/godo.go index 0713df51b..1ec4df208 100644 --- a/vendor/github.com/digitalocean/godo/godo.go +++ b/vendor/github.com/digitalocean/godo/godo.go @@ -21,7 +21,7 @@ import ( ) const ( - libraryVersion = "1.115.0" + libraryVersion = "1.116.0" defaultBaseURL = "https://api.digitalocean.com/" userAgent = "godo/" + libraryVersion mediaType = "application/json" diff --git a/vendor/github.com/digitalocean/godo/vpc_peerings.go b/vendor/github.com/digitalocean/godo/vpc_peerings.go new file mode 100644 index 000000000..e6dfc043a --- /dev/null +++ b/vendor/github.com/digitalocean/godo/vpc_peerings.go @@ -0,0 +1,199 @@ +package godo + +import ( + "context" + "net/http" + "time" +) + +const vpcPeeringsPath = "/v2/vpc_peerings" + +type vpcPeeringRoot struct { + VPCPeering *VPCPeering `json:"vpc_peering"` +} + +type vpcPeeringsRoot struct { + VPCPeerings []*VPCPeering `json:"vpc_peerings"` + Links *Links `json:"links"` + Meta *Meta `json:"meta"` +} + +// VPCPeering represents a DigitalOcean Virtual Private Cloud Peering configuration. +type VPCPeering struct { + // ID is the generated ID of the VPC Peering + ID string `json:"id"` + // Name is the name of the VPC Peering + Name string `json:"name"` + // VPCIDs is the IDs of the pair of VPCs between which a peering is created + VPCIDs []string `json:"vpc_ids"` + // CreatedAt is time when this VPC Peering was first created + CreatedAt time.Time `json:"created_at"` + // Status is the status of the VPC Peering + Status string `json:"status"` +} + +// VPCPeeringCreateRequest represents a request to create a Virtual Private Cloud Peering +// for a list of associated VPC IDs. +type VPCPeeringCreateRequest struct { + // Name is the name of the VPC Peering + Name string `json:"name"` + // VPCIDs is the IDs of the pair of VPCs between which a peering is created + VPCIDs []string `json:"vpc_ids"` +} + +// VPCPeeringUpdateRequest represents a request to update a Virtual Private Cloud Peering. +type VPCPeeringUpdateRequest struct { + // Name is the name of the VPC Peering + Name string `json:"name"` +} + +// VPCPeeringCreateRequestByVPCID represents a request to create a Virtual Private Cloud Peering +// for an associated VPC ID. +type VPCPeeringCreateRequestByVPCID struct { + // Name is the name of the VPC Peering + Name string `json:"name"` + // VPCID is the ID of one of the VPCs with which the peering has to be created + VPCID string `json:"vpc_id"` +} + +// CreateVPCPeering creates a new Virtual Private Cloud Peering. +func (v *VPCsServiceOp) CreateVPCPeering(ctx context.Context, create *VPCPeeringCreateRequest) (*VPCPeering, *Response, error) { + path := vpcPeeringsPath + req, err := v.client.NewRequest(ctx, http.MethodPost, path, create) + if err != nil { + return nil, nil, err + } + + root := new(vpcPeeringRoot) + resp, err := v.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root.VPCPeering, resp, nil +} + +// GetVPCPeering retrieves a Virtual Private Cloud Peering. +func (v *VPCsServiceOp) GetVPCPeering(ctx context.Context, id string) (*VPCPeering, *Response, error) { + path := vpcPeeringsPath + "/" + id + req, err := v.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(vpcPeeringRoot) + resp, err := v.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root.VPCPeering, resp, nil +} + +// ListVPCPeerings lists all Virtual Private Cloud Peerings. +func (v *VPCsServiceOp) ListVPCPeerings(ctx context.Context, opt *ListOptions) ([]*VPCPeering, *Response, error) { + path, err := addOptions(vpcPeeringsPath, opt) + if err != nil { + return nil, nil, err + } + req, err := v.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(vpcPeeringsRoot) + resp, err := v.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if l := root.Links; l != nil { + resp.Links = l + } + if m := root.Meta; m != nil { + resp.Meta = m + } + return root.VPCPeerings, resp, nil +} + +// UpdateVPCPeering updates a Virtual Private Cloud Peering. +func (v *VPCsServiceOp) UpdateVPCPeering(ctx context.Context, id string, update *VPCPeeringUpdateRequest) (*VPCPeering, *Response, error) { + path := vpcPeeringsPath + "/" + id + req, err := v.client.NewRequest(ctx, http.MethodPatch, path, update) + if err != nil { + return nil, nil, err + } + + root := new(vpcPeeringRoot) + resp, err := v.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root.VPCPeering, resp, nil +} + +// DeleteVPCPeering deletes a Virtual Private Cloud Peering. +func (v *VPCsServiceOp) DeleteVPCPeering(ctx context.Context, id string) (*Response, error) { + path := vpcPeeringsPath + "/" + id + req, err := v.client.NewRequest(ctx, http.MethodDelete, path, nil) + if err != nil { + return nil, err + } + + resp, err := v.client.Do(ctx, req, nil) + if err != nil { + return resp, err + } + return resp, nil +} + +// CreateVPCPeeringByVPCID creates a new Virtual Private Cloud Peering for requested VPC ID. +func (v *VPCsServiceOp) CreateVPCPeeringByVPCID(ctx context.Context, id string, create *VPCPeeringCreateRequestByVPCID) (*VPCPeering, *Response, error) { + path := vpcsBasePath + "/" + id + "/peerings" + req, err := v.client.NewRequest(ctx, http.MethodPost, path, create) + if err != nil { + return nil, nil, err + } + + root := new(vpcPeeringRoot) + resp, err := v.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root.VPCPeering, resp, nil +} + +// ListVPCPeeringsByVPCID lists all Virtual Private Cloud Peerings for requested VPC ID. +func (v *VPCsServiceOp) ListVPCPeeringsByVPCID(ctx context.Context, id string, opt *ListOptions) ([]*VPCPeering, *Response, error) { + path, err := addOptions(vpcsBasePath+"/"+id+"/peerings", opt) + req, err := v.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(vpcPeeringsRoot) + resp, err := v.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if l := root.Links; l != nil { + resp.Links = l + } + if m := root.Meta; m != nil { + resp.Meta = m + } + return root.VPCPeerings, resp, nil +} + +// UpdateVPCPeeringByVPCID updates a Virtual Private Cloud Peering for requested VPC ID. +func (v *VPCsServiceOp) UpdateVPCPeeringByVPCID(ctx context.Context, vpcID, peerID string, update *VPCPeeringUpdateRequest) (*VPCPeering, *Response, error) { + path := vpcsBasePath + "/" + vpcID + "/peerings" + "/" + peerID + req, err := v.client.NewRequest(ctx, http.MethodPatch, path, update) + if err != nil { + return nil, nil, err + } + + root := new(vpcPeeringRoot) + resp, err := v.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root.VPCPeering, resp, nil +} diff --git a/vendor/github.com/digitalocean/godo/vpcs.go b/vendor/github.com/digitalocean/godo/vpcs.go index f4f22e18e..67525190d 100644 --- a/vendor/github.com/digitalocean/godo/vpcs.go +++ b/vendor/github.com/digitalocean/godo/vpcs.go @@ -19,6 +19,14 @@ type VPCsService interface { Update(context.Context, string, *VPCUpdateRequest) (*VPC, *Response, error) Set(context.Context, string, ...VPCSetField) (*VPC, *Response, error) Delete(context.Context, string) (*Response, error) + CreateVPCPeering(context.Context, *VPCPeeringCreateRequest) (*VPCPeering, *Response, error) + GetVPCPeering(context.Context, string) (*VPCPeering, *Response, error) + ListVPCPeerings(context.Context, *ListOptions) ([]*VPCPeering, *Response, error) + UpdateVPCPeering(context.Context, string, *VPCPeeringUpdateRequest) (*VPCPeering, *Response, error) + DeleteVPCPeering(context.Context, string) (*Response, error) + CreateVPCPeeringByVPCID(context.Context, string, *VPCPeeringCreateRequestByVPCID) (*VPCPeering, *Response, error) + ListVPCPeeringsByVPCID(context.Context, string, *ListOptions) ([]*VPCPeering, *Response, error) + UpdateVPCPeeringByVPCID(context.Context, string, string, *VPCPeeringUpdateRequest) (*VPCPeering, *Response, error) } var _ VPCsService = &VPCsServiceOp{} diff --git a/vendor/modules.txt b/vendor/modules.txt index cb7dee754..3f9b348a0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -58,7 +58,7 @@ github.com/creack/pty # github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc ## explicit github.com/davecgh/go-spew/spew -# github.com/digitalocean/godo v1.115.0 +# github.com/digitalocean/godo v1.116.0 ## explicit; go 1.20 github.com/digitalocean/godo github.com/digitalocean/godo/metrics