Skip to content

Commit 1994e1e

Browse files
chore: Migrate gateways command from packngo to metal-go client (equinix#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]>
1 parent a051f74 commit 1994e1e

14 files changed

+490
-56
lines changed

docs/metal_gateway_create.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ metal gateway create -p <project_UUID> --virtual-network <virtual_network_UUID>
2323
### Options
2424

2525
```
26-
-h, --help help for create
27-
-r, --ip-reservation-id string UUID of the Public or VRF IP Reservation to assign.
28-
-s, --private-subnet-size int Size of the private subnet to request (8 for /29)
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.
30-
-v, --virtual-network string UUID of the Virtual Network to assign.
26+
-h, --help help for create
27+
-r, --ip-reservation-id string UUID of the Public or VRF IP Reservation to assign.
28+
-s, --private-subnet-size int32 Size of the private subnet to request (8 for /29)
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.
30+
-v, --virtual-network string UUID of the Virtual Network to assign.
3131
```
3232

3333
### Options inherited from parent commands

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require (
77
github.com/manifoldco/promptui v0.9.0
88
github.com/olekukonko/tablewriter v0.0.5
99
github.com/packethost/packngo v0.30.0
10+
github.com/pkg/errors v0.9.1
1011
github.com/spf13/cobra v1.8.0
1112
github.com/spf13/pflag v1.0.5
1213
github.com/spf13/viper v1.17.0

go.sum

+1-4
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ github.com/packethost/packngo v0.30.0 h1:JVeTwbXXETsLTDQncUbYwIFpkOp/xevXrffM2Hr
159159
github.com/packethost/packngo v0.30.0/go.mod h1:BT/XcdwLVmeMtGPbovnxCpnI1s9ylSE1cs/7pq007NE=
160160
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
161161
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
162+
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
162163
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
163164
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
164165
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -257,8 +258,6 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
257258
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
258259
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
259260
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
260-
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
261-
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
262261
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
263262
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
264263
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -412,8 +411,6 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f
412411
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
413412
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
414413
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
415-
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
416-
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
417414
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
418415
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
419416
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

internal/gateway/create.go

+32-12
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,18 @@
2121
package gateway
2222

2323
import (
24+
"context"
25+
"errors"
2426
"fmt"
2527
"strconv"
2628

27-
"github.com/packethost/packngo"
29+
metal "github.com/equinix-labs/metal-go/metal/v1"
2830
"github.com/spf13/cobra"
2931
)
3032

3133
func (c *Client) Create() *cobra.Command {
3234
var projectID, vnID, reservationID string
33-
var netSize int
35+
var netSize int32
3436

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

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

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

55-
n, _, err := c.Service.Create(projectID, req)
56+
req := metal.CreateMetalGatewayRequest{
57+
MetalGatewayCreateInput: &metal.MetalGatewayCreateInput{
58+
VirtualNetworkId: vnID,
59+
},
60+
}
61+
if reservationID != "" {
62+
req.MetalGatewayCreateInput.SetIpReservationId(reservationID)
63+
} else {
64+
req.MetalGatewayCreateInput.SetPrivateIpv4SubnetSize(netSize)
65+
}
66+
67+
n, _, err := c.Service.
68+
CreateMetalGateway(context.Background(), projectID).
69+
Include(c.Servicer.Includes(includes)).
70+
Exclude(c.Servicer.Excludes(nil)).
71+
CreateMetalGatewayRequest(req).
72+
Execute()
5673
if err != nil {
5774
return fmt.Errorf("Could not create Metal Gateway: %w", err)
5875
}
5976

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

63-
if n.IPReservation != nil {
64-
address = n.IPReservation.Address + "/" + strconv.Itoa(n.IPReservation.CIDR)
80+
gway := n.MetalGateway
81+
ipReservation := gway.IpReservation
82+
if ipReservation != nil {
83+
address = ipReservation.GetAddress() + "/" + strconv.Itoa(int(ipReservation.GetCidr()))
6584
}
6685

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

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

71-
return c.Out.Output(n, header, &data)
91+
return c.Out.Output(gway, header, &data)
7292
},
7393
}
7494

7595
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.")
7696
createMetalGatewayCmd.Flags().StringVarP(&reservationID, "ip-reservation-id", "r", "", "UUID of the Public or VRF IP Reservation to assign.")
7797
createMetalGatewayCmd.Flags().StringVarP(&vnID, "virtual-network", "v", "", "UUID of the Virtual Network to assign.")
78-
createMetalGatewayCmd.Flags().IntVarP(&netSize, "private-subnet-size", "s", 0, "Size of the private subnet to request (8 for /29)")
98+
createMetalGatewayCmd.Flags().Int32VarP(&netSize, "private-subnet-size", "s", 0, "Size of the private subnet to request (8 for /29)")
7999

80100
_ = createMetalGatewayCmd.MarkFlagRequired("project-id")
81101
_ = createMetalGatewayCmd.MarkFlagRequired("virtual-network")

internal/gateway/delete.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
package gateway
2222

2323
import (
24+
"context"
2425
"fmt"
2526

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

3638
deleteGway := func(id string) error {
37-
_, err := c.Service.Delete(id)
39+
_, _, err := c.Service.
40+
DeleteMetalGateway(context.Background(), id).
41+
Include(c.Servicer.Includes(includes)).
42+
Exclude(c.Servicer.Excludes(nil)).
43+
Execute()
3844
if err != nil {
3945
return err
4046
}

internal/gateway/gateway.go

+8-5
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,14 @@ package gateway
2222

2323
import (
2424
"github.com/equinix/metal-cli/internal/outputs"
25-
"github.com/packethost/packngo"
25+
26+
metal "github.com/equinix-labs/metal-go/metal/v1"
2627
"github.com/spf13/cobra"
2728
)
2829

2930
type Client struct {
3031
Servicer Servicer
31-
Service packngo.MetalGatewayService
32+
Service *metal.MetalGatewaysApiService
3233
Out outputs.Outputer
3334
}
3435

@@ -45,7 +46,7 @@ func (c *Client) NewCommand() *cobra.Command {
4546
root.PersistentPreRun(cmd, args)
4647
}
4748
}
48-
c.Service = c.Servicer.API(cmd).MetalGateways
49+
c.Service = c.Servicer.MetalAPI(cmd).MetalGatewaysApi
4950
},
5051
}
5152

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

6061
type Servicer interface {
61-
API(*cobra.Command) *packngo.Client
62-
ListOptions(defaultIncludes, defaultExcludes []string) *packngo.ListOptions
62+
MetalAPI(*cobra.Command) *metal.APIClient
63+
Filters() map[string]string
64+
Includes(defaultIncludes []string) (incl []string)
65+
Excludes(defaultExcludes []string) (excl []string)
6366
}
6467

6568
func NewClient(s Servicer, out outputs.Outputer) *Client {

internal/gateway/retrieve.go

+22-6
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@
2121
package gateway
2222

2323
import (
24+
"context"
2425
"fmt"
2526
"strconv"
2627

28+
metal "github.com/equinix-labs/metal-go/metal/v1"
29+
2730
"github.com/spf13/cobra"
2831
)
2932

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

4346
RunE: func(cmd *cobra.Command, args []string) error {
4447
cmd.SilenceUsage = true
45-
listOpts := c.Servicer.ListOptions(nil, nil).Including("virtual_network", "ip_reservation")
46-
gways, _, err := c.Service.List(projectID, listOpts)
48+
includes := []string{"virtual_network", "ip_reservation"}
49+
50+
gwayList, _, err := c.Service.
51+
FindMetalGatewaysByProject(context.Background(), projectID).
52+
Include(c.Servicer.Includes(includes)).
53+
Exclude(c.Servicer.Excludes(nil)).
54+
Execute()
4755
if err != nil {
4856
return fmt.Errorf("Could not list Project Metal Gateways: %w", err)
4957
}
5058

59+
gways := gwayList.GetMetalGateways()
60+
5161
data := make([][]string, len(gways))
62+
metalGways := make([]*metal.MetalGateway, len(gways))
5263

5364
for i, n := range gways {
65+
gway := n.MetalGateway
66+
metalGways = append(metalGways, gway)
67+
5468
address := ""
5569

56-
if n.IPReservation != nil {
57-
address = n.IPReservation.Address + "/" + strconv.Itoa(n.IPReservation.CIDR)
70+
ipReservation := gway.IpReservation
71+
if ipReservation != nil {
72+
address = ipReservation.GetAddress() + "/" + strconv.Itoa(int(ipReservation.GetCidr()))
5873
}
5974

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

64-
return c.Out.Output(gways, header, &data)
80+
return c.Out.Output(metalGways, header, &data)
6581
},
6682
}
6783
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.")

test/e2e/gateways/create_test.go

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package gateways
2+
3+
import (
4+
"context"
5+
"errors"
6+
"io"
7+
"os"
8+
"strconv"
9+
"strings"
10+
"testing"
11+
12+
"github.com/spf13/cobra"
13+
14+
root "github.com/equinix/metal-cli/internal/cli"
15+
"github.com/equinix/metal-cli/internal/gateway"
16+
outputPkg "github.com/equinix/metal-cli/internal/outputs"
17+
"github.com/equinix/metal-cli/test/helper"
18+
)
19+
20+
func TestGateways_Create(t *testing.T) {
21+
var projectId, deviceId string
22+
subCommand := "gateways"
23+
consumerToken := ""
24+
apiURL := ""
25+
Version := "devel"
26+
rootClient := root.NewClient(consumerToken, apiURL, Version)
27+
28+
device := helper.SetupProjectAndDevice(t, &projectId, &deviceId)
29+
t.Cleanup(func() {
30+
if err := helper.CleanupProjectAndDevice(deviceId, projectId); err != nil &&
31+
!strings.Contains(err.Error(), "Not Found") {
32+
t.Error(err)
33+
}
34+
})
35+
if device == nil {
36+
return
37+
}
38+
39+
vlan, err := helper.CreateTestVLAN(projectId)
40+
t.Cleanup(func() {
41+
if err := helper.CleanTestVlan(vlan.GetId()); err != nil &&
42+
!strings.Contains(err.Error(), "Not Found") {
43+
t.Error(err)
44+
}
45+
})
46+
if err != nil {
47+
t.Error(err)
48+
return
49+
}
50+
51+
tests := []struct {
52+
name string
53+
cmd *cobra.Command
54+
want *cobra.Command
55+
cmdFunc func(*testing.T, *cobra.Command)
56+
}{
57+
{
58+
name: "create gateways",
59+
cmd: gateway.NewClient(rootClient, outputPkg.Outputer(&outputPkg.Standard{})).NewCommand(),
60+
want: &cobra.Command{},
61+
cmdFunc: func(t *testing.T, c *cobra.Command) {
62+
root := c.Root()
63+
64+
root.SetArgs([]string{subCommand, "create", "-p", projectId, "-v", vlan.GetId(), "-s", "8"})
65+
66+
rescueStdout := os.Stdout
67+
r, w, _ := os.Pipe()
68+
os.Stdout = w
69+
if err := root.Execute(); err != nil {
70+
t.Error(err)
71+
}
72+
w.Close()
73+
out, _ := io.ReadAll(r)
74+
os.Stdout = rescueStdout
75+
76+
apiClient := helper.TestClient()
77+
gateways, _, err := apiClient.MetalGatewaysApi.
78+
FindMetalGatewaysByProject(context.Background(), projectId).
79+
Execute()
80+
if err != nil {
81+
t.Error(err)
82+
return
83+
}
84+
if len(gateways.MetalGateways) != 1 {
85+
t.Error(errors.New("Gateway Not Found. Failed to create gateway"))
86+
return
87+
}
88+
89+
assertGatewaysCmdOutput(t, string(out[:]), gateways.MetalGateways[0].MetalGateway.GetId(), device.Metro.GetCode(), strconv.Itoa(int(vlan.GetVxlan())))
90+
},
91+
},
92+
}
93+
94+
for _, tt := range tests {
95+
t.Run(tt.name, func(t *testing.T) {
96+
rootCmd := rootClient.NewCommand()
97+
rootCmd.AddCommand(tt.cmd)
98+
tt.cmdFunc(t, tt.cmd)
99+
})
100+
}
101+
}
102+
103+
func assertGatewaysCmdOutput(t *testing.T, out, gatewayId, metro, vxlan string) {
104+
if !strings.Contains(out, gatewayId) {
105+
t.Errorf("cmd output should contain ID of the gateway: [%s] \n output:\n%s", gatewayId, out)
106+
}
107+
108+
if !strings.Contains(out, metro) {
109+
t.Errorf("cmd output should contain metro same as device: [%s] \n output:\n%s", metro, out)
110+
}
111+
112+
if !strings.Contains(out, vxlan) {
113+
t.Errorf("cmd output should contain vxlan, gateway is attached with: [%s] \n output:\n%s", vxlan, out)
114+
}
115+
116+
if !strings.Contains(out, "ready") {
117+
t.Errorf("cmd output should contain 'ready' state of the gateway, output:\n%s", out)
118+
}
119+
}

0 commit comments

Comments
 (0)