Skip to content

Commit

Permalink
chore: Migrate gateways command from packngo to metal-go client (equi…
Browse files Browse the repository at this point in the history
…nix#376)

Issue Task as part of migrating metal-cli from packngo to metal-go
client, added the support of gateways subcommand to use metal-go
Fixes: equinix#333

---------

Signed-off-by: Ayush Rangwala <[email protected]>
  • Loading branch information
aayushrangwala authored Nov 22, 2023
1 parent a051f74 commit 1994e1e
Show file tree
Hide file tree
Showing 14 changed files with 490 additions and 56 deletions.
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)
-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

0 comments on commit 1994e1e

Please sign in to comment.