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
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
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
118 changes: 118 additions & 0 deletions test/e2e/gateways/create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
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, err := helper.SetupProjectAndDevice(t, &projectId, &deviceId)
defer func() {
Copy link
Contributor Author

@aayushrangwala aayushrangwala Nov 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use t.Cleanup() refer here

if err := helper.CleanupProjectAndDevice(deviceId, projectId); err != nil {
t.Error(err)
}
}()

if err != nil {
return
}

vlan, err := helper.CreateTestVLAN(projectId)
if err != nil {
t.Error(err)
return
}
defer func() {
if err := helper.CleanTestVlan(vlan.GetId()); err != nil {
t.Error(err)
}
}()

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", gatewayId)
}

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

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

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