Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

chore: Migrate gateways command from packngo to metal-go client #376

Merged
merged 9 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions docs/metal_gateway_create.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ metal gateway create -p <project_UUID> --virtual-network <virtual_network_UUID>
### Options

```
-h, --help help for create
-r, --ip-reservation-id string UUID of the Public or VRF IP Reservation to assign.
-s, --private-subnet-size int Size of the private subnet to request (8 for /29)
-p, --project-id string The project's UUID. This flag is required, unless specified in the config created by metal init or set as METAL_PROJECT_ID environment variable.
-v, --virtual-network string UUID of the Virtual Network to assign.
-h, --help help for create
-r, --ip-reservation-id string UUID of the Public or VRF IP Reservation to assign.
-s, --private-subnet-size int32 Size of the private subnet to request (8 for /29)
aayushrangwala marked this conversation as resolved.
Show resolved Hide resolved
-p, --project-id string The project's UUID. This flag is required, unless specified in the config created by metal init or set as METAL_PROJECT_ID environment variable.
-v, --virtual-network string UUID of the Virtual Network to assign.
```

### Options inherited from parent commands
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/manifoldco/promptui v0.9.0
github.com/olekukonko/tablewriter v0.0.5
github.com/packethost/packngo v0.30.0
github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.17.0
Expand Down
5 changes: 1 addition & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ github.com/packethost/packngo v0.30.0 h1:JVeTwbXXETsLTDQncUbYwIFpkOp/xevXrffM2Hr
github.com/packethost/packngo v0.30.0/go.mod h1:BT/XcdwLVmeMtGPbovnxCpnI1s9ylSE1cs/7pq007NE=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand Down Expand Up @@ -257,8 +258,6 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand Down Expand Up @@ -412,8 +411,6 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
44 changes: 32 additions & 12 deletions internal/gateway/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,18 @@
package gateway

import (
"context"
"errors"
"fmt"
"strconv"

"github.com/packethost/packngo"
metal "github.com/equinix-labs/metal-go/metal/v1"
"github.com/spf13/cobra"
)

func (c *Client) Create() *cobra.Command {
var projectID, vnID, reservationID string
var netSize int
var netSize int32

// createMetalGatewayCmd represents the createMetalGateway command
createMetalGatewayCmd := &cobra.Command{
Expand All @@ -45,37 +47,55 @@ func (c *Client) Create() *cobra.Command {

RunE: func(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true
includes := []string{"virtual_network", "ip_reservation"}

req := &packngo.MetalGatewayCreateRequest{
VirtualNetworkID: vnID,
IPReservationID: reservationID,
PrivateIPv4SubnetSize: netSize,
if reservationID == "" && netSize == 0 {
return errors.New("Invalid input. Provide either 'private-subnet-size' or 'ip-reservation-id'")
}

n, _, err := c.Service.Create(projectID, req)
req := metal.CreateMetalGatewayRequest{
MetalGatewayCreateInput: &metal.MetalGatewayCreateInput{
VirtualNetworkId: vnID,
},
}
if reservationID != "" {
req.MetalGatewayCreateInput.SetIpReservationId(reservationID)
} else {
req.MetalGatewayCreateInput.SetPrivateIpv4SubnetSize(netSize)
}

n, _, err := c.Service.
CreateMetalGateway(context.Background(), projectID).
Include(c.Servicer.Includes(includes)).
Exclude(c.Servicer.Excludes(nil)).
CreateMetalGatewayRequest(req).
Execute()
if err != nil {
return fmt.Errorf("Could not create Metal Gateway: %w", err)
}

data := make([][]string, 1)
address := ""

if n.IPReservation != nil {
address = n.IPReservation.Address + "/" + strconv.Itoa(n.IPReservation.CIDR)
gway := n.MetalGateway
ipReservation := gway.IpReservation
if ipReservation != nil {
address = ipReservation.GetAddress() + "/" + strconv.Itoa(int(ipReservation.GetCidr()))
}

data[0] = []string{n.ID, n.VirtualNetwork.MetroCode, strconv.Itoa(n.VirtualNetwork.VXLAN), address, string(n.State), n.CreatedAt}
data[0] = []string{gway.GetId(), gway.VirtualNetwork.GetMetroCode(),
strconv.Itoa(int(gway.VirtualNetwork.GetVxlan())), address, string(gway.GetState()), gway.GetCreatedAt().String()}

header := []string{"ID", "Metro", "VXLAN", "Addresses", "State", "Created"}

return c.Out.Output(n, header, &data)
return c.Out.Output(gway, header, &data)
},
}

createMetalGatewayCmd.Flags().StringVarP(&projectID, "project-id", "p", "", "The project's UUID. This flag is required, unless specified in the config created by metal init or set as METAL_PROJECT_ID environment variable.")
createMetalGatewayCmd.Flags().StringVarP(&reservationID, "ip-reservation-id", "r", "", "UUID of the Public or VRF IP Reservation to assign.")
createMetalGatewayCmd.Flags().StringVarP(&vnID, "virtual-network", "v", "", "UUID of the Virtual Network to assign.")
createMetalGatewayCmd.Flags().IntVarP(&netSize, "private-subnet-size", "s", 0, "Size of the private subnet to request (8 for /29)")
createMetalGatewayCmd.Flags().Int32VarP(&netSize, "private-subnet-size", "s", 0, "Size of the private subnet to request (8 for /29)")

_ = createMetalGatewayCmd.MarkFlagRequired("project-id")
_ = createMetalGatewayCmd.MarkFlagRequired("virtual-network")
Expand Down
8 changes: 7 additions & 1 deletion internal/gateway/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package gateway

import (
"context"
"fmt"

"github.com/manifoldco/promptui"
Expand All @@ -32,9 +33,14 @@ func (c *Client) Delete() *cobra.Command {
gwayID string
force bool
)
includes := []string{"virtual_network", "ip_reservation"}

deleteGway := func(id string) error {
_, err := c.Service.Delete(id)
_, _, err := c.Service.
DeleteMetalGateway(context.Background(), id).
Include(c.Servicer.Includes(includes)).
Exclude(c.Servicer.Excludes(nil)).
Execute()
if err != nil {
return err
}
Expand Down
13 changes: 8 additions & 5 deletions internal/gateway/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ package gateway

import (
"github.com/equinix/metal-cli/internal/outputs"
"github.com/packethost/packngo"

metal "github.com/equinix-labs/metal-go/metal/v1"
"github.com/spf13/cobra"
)

type Client struct {
Servicer Servicer
Service packngo.MetalGatewayService
Service *metal.MetalGatewaysApiService
Out outputs.Outputer
}

Expand All @@ -45,7 +46,7 @@ func (c *Client) NewCommand() *cobra.Command {
root.PersistentPreRun(cmd, args)
}
}
c.Service = c.Servicer.API(cmd).MetalGateways
c.Service = c.Servicer.MetalAPI(cmd).MetalGatewaysApi
},
}

Expand All @@ -58,8 +59,10 @@ func (c *Client) NewCommand() *cobra.Command {
}

type Servicer interface {
API(*cobra.Command) *packngo.Client
ListOptions(defaultIncludes, defaultExcludes []string) *packngo.ListOptions
MetalAPI(*cobra.Command) *metal.APIClient
Filters() map[string]string
Includes(defaultIncludes []string) (incl []string)
Excludes(defaultExcludes []string) (excl []string)
}

func NewClient(s Servicer, out outputs.Outputer) *Client {
Expand Down
28 changes: 22 additions & 6 deletions internal/gateway/retrieve.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@
package gateway

import (
"context"
"fmt"
"strconv"

metal "github.com/equinix-labs/metal-go/metal/v1"

"github.com/spf13/cobra"
)

Expand All @@ -42,26 +45,39 @@ func (c *Client) Retrieve() *cobra.Command {

RunE: func(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true
listOpts := c.Servicer.ListOptions(nil, nil).Including("virtual_network", "ip_reservation")
gways, _, err := c.Service.List(projectID, listOpts)
includes := []string{"virtual_network", "ip_reservation"}

gwayList, _, err := c.Service.
FindMetalGatewaysByProject(context.Background(), projectID).
Include(c.Servicer.Includes(includes)).
Exclude(c.Servicer.Excludes(nil)).
Execute()
if err != nil {
return fmt.Errorf("Could not list Project Metal Gateways: %w", err)
}

gways := gwayList.GetMetalGateways()

data := make([][]string, len(gways))
metalGways := make([]*metal.MetalGateway, len(gways))

for i, n := range gways {
gway := n.MetalGateway
metalGways = append(metalGways, gway)

address := ""

if n.IPReservation != nil {
address = n.IPReservation.Address + "/" + strconv.Itoa(n.IPReservation.CIDR)
ipReservation := gway.IpReservation
if ipReservation != nil {
address = ipReservation.GetAddress() + "/" + strconv.Itoa(int(ipReservation.GetCidr()))
}

data[i] = []string{n.ID, n.VirtualNetwork.MetroCode, strconv.Itoa(n.VirtualNetwork.VXLAN), address, string(n.State), n.CreatedAt}
data[i] = []string{gway.GetId(), gway.VirtualNetwork.GetMetroCode(), strconv.Itoa(int(gway.VirtualNetwork.GetVxlan())),
address, string(gway.GetState()), gway.GetCreatedAt().String()}
}
header := []string{"ID", "Metro", "VXLAN", "Addresses", "State", "Created"}

return c.Out.Output(gways, header, &data)
return c.Out.Output(metalGways, header, &data)
},
}
retrieveMetalGatewaysCmd.Flags().StringVarP(&projectID, "project-id", "p", "", "The project's UUID. This flag is required, unless specified in the config created by metal init or set as METAL_PROJECT_ID environment variable.")
Expand Down
119 changes: 119 additions & 0 deletions test/e2e/gateways/create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package gateways

import (
"context"
"errors"
"io"
"os"
"strconv"
"strings"
"testing"

"github.com/spf13/cobra"

root "github.com/equinix/metal-cli/internal/cli"
"github.com/equinix/metal-cli/internal/gateway"
outputPkg "github.com/equinix/metal-cli/internal/outputs"
"github.com/equinix/metal-cli/test/helper"
)

func TestGateways_Create(t *testing.T) {
var projectId, deviceId string
subCommand := "gateways"
consumerToken := ""
apiURL := ""
Version := "devel"
rootClient := root.NewClient(consumerToken, apiURL, Version)

device := helper.SetupProjectAndDevice(t, &projectId, &deviceId)
t.Cleanup(func() {
if err := helper.CleanupProjectAndDevice(deviceId, projectId); err != nil &&
!strings.Contains(err.Error(), "Not Found") {
t.Error(err)
}
})
if device == nil {
return
}

vlan, err := helper.CreateTestVLAN(projectId)
t.Cleanup(func() {
if err := helper.CleanTestVlan(vlan.GetId()); err != nil &&
!strings.Contains(err.Error(), "Not Found") {
t.Error(err)
}
})
if err != nil {
t.Error(err)
return
}

tests := []struct {
name string
cmd *cobra.Command
want *cobra.Command
cmdFunc func(*testing.T, *cobra.Command)
}{
{
name: "create gateways",
cmd: gateway.NewClient(rootClient, outputPkg.Outputer(&outputPkg.Standard{})).NewCommand(),
want: &cobra.Command{},
cmdFunc: func(t *testing.T, c *cobra.Command) {
root := c.Root()

root.SetArgs([]string{subCommand, "create", "-p", projectId, "-v", vlan.GetId(), "-s", "8"})

rescueStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
if err := root.Execute(); err != nil {
t.Error(err)
}
w.Close()
out, _ := io.ReadAll(r)
os.Stdout = rescueStdout

apiClient := helper.TestClient()
gateways, _, err := apiClient.MetalGatewaysApi.
FindMetalGatewaysByProject(context.Background(), projectId).
Execute()
if err != nil {
t.Error(err)
return
}
if len(gateways.MetalGateways) != 1 {
t.Error(errors.New("Gateway Not Found. Failed to create gateway"))
return
}

assertGatewaysCmdOutput(t, string(out[:]), gateways.MetalGateways[0].MetalGateway.GetId(), device.Metro.GetCode(), strconv.Itoa(int(vlan.GetVxlan())))
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rootCmd := rootClient.NewCommand()
rootCmd.AddCommand(tt.cmd)
tt.cmdFunc(t, tt.cmd)
})
}
}

func assertGatewaysCmdOutput(t *testing.T, out, gatewayId, metro, vxlan string) {
if !strings.Contains(out, gatewayId) {
t.Errorf("cmd output should contain ID of the gateway: [%s] \n output:\n%s", gatewayId, out)
}

if !strings.Contains(out, metro) {
t.Errorf("cmd output should contain metro same as device: [%s] \n output:\n%s", metro, out)
}

if !strings.Contains(out, vxlan) {
t.Errorf("cmd output should contain vxlan, gateway is attached with: [%s] \n output:\n%s", vxlan, out)
}

if !strings.Contains(out, "ready") {
t.Errorf("cmd output should contain 'ready' state of the gateway, output:\n%s", out)
}
}
Loading
Loading