Skip to content

Commit 7a76c3c

Browse files
authored
feat(gateway): add commands for list & delete (#289)
1 parent b27d42c commit 7a76c3c

11 files changed

Lines changed: 402 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
- Add `gateway` commands (`delete`, `list`) for Network gateway management.
12+
1013
## [3.3.0] - 2024-01-23
1114

1215
### Added

internal/commands/all/all.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
databaseindex "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/database/index"
99
databaseproperties "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/database/properties"
1010
databasesession "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/database/session"
11+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/gateway"
1112
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/ipaddress"
1213
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/kubernetes"
1314
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/kubernetes/nodegroup"
@@ -188,6 +189,11 @@ func BuildCommands(rootCmd *cobra.Command, conf *config.Config) {
188189
commands.BuildCommand(objectstorage.ListCommand(), objectStorageCommand.Cobra(), conf)
189190
commands.BuildCommand(objectstorage.ShowCommand(), objectStorageCommand.Cobra(), conf)
190191

192+
// Network Gateway operations
193+
gatewayCommand := commands.BuildCommand(gateway.BasegatewayCommand(), rootCmd, conf)
194+
commands.BuildCommand(gateway.DeleteCommand(), gatewayCommand.Cobra(), conf)
195+
commands.BuildCommand(gateway.ListCommand(), gatewayCommand.Cobra(), conf)
196+
191197
// Misc
192198
commands.BuildCommand(
193199
&root.VersionCommand{
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package gateway
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands"
7+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/completion"
8+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/output"
9+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/resolver"
10+
11+
"github.com/UpCloudLtd/upcloud-go-api/v6/upcloud/request"
12+
)
13+
14+
// DeleteCommand creates the "gateway delete" command
15+
func DeleteCommand() commands.Command {
16+
return &deleteCommand{
17+
BaseCommand: commands.New(
18+
"delete",
19+
"Delete a gateway",
20+
"upctl gateway delete 8abc8009-4325-4b23-4321-b1232cd81231",
21+
"upctl gateway delete my-gateway",
22+
),
23+
}
24+
}
25+
26+
type deleteCommand struct {
27+
*commands.BaseCommand
28+
resolver.CachingGateway
29+
completion.Gateway
30+
}
31+
32+
// Execute implements commands.MultipleArgumentCommand
33+
func (c *deleteCommand) Execute(exec commands.Executor, arg string) (output.Output, error) {
34+
svc := exec.All()
35+
msg := fmt.Sprintf("Deleting gateway %v", arg)
36+
exec.PushProgressStarted(msg)
37+
38+
err := svc.DeleteGateway(exec.Context(), &request.DeleteGatewayRequest{
39+
UUID: arg,
40+
})
41+
if err != nil {
42+
return commands.HandleError(exec, msg, err)
43+
}
44+
45+
exec.PushProgressSuccess(msg)
46+
47+
return output.None{}, nil
48+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package gateway
2+
3+
import (
4+
"testing"
5+
6+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands"
7+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/config"
8+
smock "github.com/UpCloudLtd/upcloud-cli/v3/internal/mock"
9+
10+
"github.com/UpCloudLtd/upcloud-go-api/v6/upcloud"
11+
"github.com/UpCloudLtd/upcloud-go-api/v6/upcloud/request"
12+
"github.com/gemalto/flume"
13+
"github.com/stretchr/testify/assert"
14+
)
15+
16+
func TestDeleteCommand(t *testing.T) {
17+
targetMethod := "DeleteGateway"
18+
19+
gateway := upcloud.Gateway{
20+
Name: "test-gateway",
21+
UUID: "17fbd082-30b0-11eb-adc1-0242ac120003",
22+
}
23+
24+
for _, test := range []struct {
25+
name string
26+
arg string
27+
error string
28+
req request.DeleteGatewayRequest
29+
}{
30+
{
31+
name: "delete with UUID",
32+
arg: gateway.UUID,
33+
req: request.DeleteGatewayRequest{UUID: gateway.UUID},
34+
},
35+
} {
36+
t.Run(test.name, func(t *testing.T) {
37+
mService := smock.Service{}
38+
mService.On(targetMethod, &test.req).Return(nil)
39+
40+
conf := config.New()
41+
c := commands.BuildCommand(DeleteCommand(), nil, conf)
42+
43+
_, err := c.(commands.MultipleArgumentCommand).Execute(commands.NewExecutor(conf, &mService, flume.New("test")), test.arg)
44+
45+
if test.error != "" {
46+
assert.EqualError(t, err, test.error)
47+
} else {
48+
assert.NoError(t, err)
49+
mService.AssertNumberOfCalls(t, targetMethod, 1)
50+
}
51+
})
52+
}
53+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package gateway
2+
3+
import (
4+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands"
5+
)
6+
7+
// BasegatewayCommand creates the base "gateway" command
8+
func BasegatewayCommand() commands.Command {
9+
return &gatewayCommand{
10+
commands.New("gateway", "Manage gateways"),
11+
}
12+
}
13+
14+
type gatewayCommand struct {
15+
*commands.BaseCommand
16+
}

internal/commands/gateway/list.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package gateway
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands"
8+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/output"
9+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/ui"
10+
"github.com/UpCloudLtd/upcloud-go-api/v6/upcloud"
11+
"github.com/jedib0t/go-pretty/v6/text"
12+
)
13+
14+
// ListCommand creates the "gateway list" command
15+
func ListCommand() commands.Command {
16+
return &listCommand{
17+
BaseCommand: commands.New("list", "List gateways", "upctl gateway list"),
18+
}
19+
}
20+
21+
type listCommand struct {
22+
*commands.BaseCommand
23+
}
24+
25+
// ExecuteWithoutArguments implements commands.NoArgumentCommand
26+
func (c *listCommand) ExecuteWithoutArguments(exec commands.Executor) (output.Output, error) {
27+
svc := exec.All()
28+
gateways, err := svc.GetGateways(exec.Context())
29+
if err != nil {
30+
return nil, err
31+
}
32+
33+
rows := []output.TableRow{}
34+
for _, gtw := range gateways {
35+
rows = append(rows, output.TableRow{
36+
gtw.UUID,
37+
gtw.Name,
38+
gtw.Routers,
39+
gtw.OperationalState,
40+
gtw.Zone,
41+
})
42+
}
43+
44+
// For JSON and YAML output, passthrough API response
45+
return output.MarshaledWithHumanOutput{
46+
Value: gateways,
47+
Output: output.Table{
48+
Columns: []output.TableColumn{
49+
{Key: "uuid", Header: "UUID", Colour: ui.DefaultUUUIDColours},
50+
{Key: "name", Header: "Name"},
51+
{Key: "routers", Header: "Routers", Format: formatRouters},
52+
{Key: "status", Header: "Status"},
53+
{Key: "zone", Header: "Zone"},
54+
},
55+
Rows: rows,
56+
},
57+
}, nil
58+
}
59+
60+
func formatRouters(val interface{}) (text.Colors, string, error) {
61+
routers, ok := val.([]upcloud.GatewayRouter)
62+
if !ok {
63+
return nil, "", fmt.Errorf("cannot parse routers from %T, expected []upcloud.GatewayRouter", val)
64+
}
65+
66+
var rows []string
67+
for _, rt := range routers {
68+
rows = append(rows, ui.DefaultUUUIDColours.Sprint(rt.UUID))
69+
}
70+
71+
return nil, strings.Join(rows, ",\n"), nil
72+
}

internal/completion/gateway.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package completion
2+
3+
import (
4+
"context"
5+
6+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/service"
7+
"github.com/spf13/cobra"
8+
)
9+
10+
// Gateway implements argument completion for gateways, by uuid or name.
11+
type Gateway struct{}
12+
13+
// make sure LoadBalancer implements the interface
14+
var _ Provider = Gateway{}
15+
16+
// CompleteArgument implements completion.Provider
17+
func (s Gateway) CompleteArgument(ctx context.Context, svc service.AllServices, toComplete string) ([]string, cobra.ShellCompDirective) {
18+
gateways, err := svc.GetGateways(ctx)
19+
if err != nil {
20+
return None(toComplete)
21+
}
22+
var vals []string
23+
for _, gtw := range gateways {
24+
vals = append(vals, gtw.UUID, gtw.Name)
25+
}
26+
27+
return MatchStringPrefix(vals, toComplete, true), cobra.ShellCompDirectiveNoFileComp
28+
}

internal/mock/mock.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ var (
6565
_ service.Kubernetes = &Service{}
6666
_ service.ServerGroup = &Service{}
6767
_ service.ManagedObjectStorage = &Service{}
68+
_ service.Gateway = &Service{}
6869
)
6970

7071
// GetServerConfigurations implements service.Server.GetServerConfigurations
@@ -1208,3 +1209,45 @@ func (m *Service) RevokePermission(ctx context.Context, r *request.RevokePermiss
12081209
}
12091210
return nil
12101211
}
1212+
1213+
// GetGateways implements service.Gateway.GetGateways
1214+
func (m *Service) GetGateways(_ context.Context, f ...request.QueryFilter) ([]upcloud.Gateway, error) {
1215+
args := m.Called(f)
1216+
if args[0] == nil {
1217+
return nil, args.Error(1)
1218+
}
1219+
return args[0].([]upcloud.Gateway), args.Error(1)
1220+
}
1221+
1222+
// DeleteGateway implements service.Gateway.DeleteGateway
1223+
func (m *Service) DeleteGateway(_ context.Context, r *request.DeleteGatewayRequest) error {
1224+
args := m.Called(r)
1225+
return args.Error(0)
1226+
}
1227+
1228+
// CreateGateway implements service.Gateway.CreateGateway
1229+
func (m *Service) CreateGateway(_ context.Context, r *request.CreateGatewayRequest) (*upcloud.Gateway, error) {
1230+
args := m.Called(r)
1231+
if args[0] == nil {
1232+
return nil, args.Error(1)
1233+
}
1234+
return args[0].(*upcloud.Gateway), args.Error(1)
1235+
}
1236+
1237+
// GetGateway implements service.Gateway.GetGateway
1238+
func (m *Service) GetGateway(_ context.Context, r *request.GetGatewayRequest) (*upcloud.Gateway, error) {
1239+
args := m.Called(r)
1240+
if args[0] == nil {
1241+
return nil, args.Error(1)
1242+
}
1243+
return args[0].(*upcloud.Gateway), args.Error(1)
1244+
}
1245+
1246+
// ModifyGateway implements service.Gateway.ModifyGateway
1247+
func (m *Service) ModifyGateway(_ context.Context, r *request.ModifyGatewayRequest) (*upcloud.Gateway, error) {
1248+
args := m.Called(r)
1249+
if args[0] == nil {
1250+
return nil, args.Error(1)
1251+
}
1252+
return args[0].(*upcloud.Gateway), args.Error(1)
1253+
}

internal/resolver/gateway.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package resolver
2+
3+
import (
4+
"context"
5+
6+
internal "github.com/UpCloudLtd/upcloud-cli/v3/internal/service"
7+
)
8+
9+
// CachingGatewayimplements resolver for gateways, caching the results
10+
type CachingGateway struct{}
11+
12+
// make sure we implement the ResolutionProvider interface
13+
var _ ResolutionProvider = CachingGateway{}
14+
15+
// Get implements ResolutionProvider.Get
16+
func (s CachingGateway) Get(ctx context.Context, svc internal.AllServices) (Resolver, error) {
17+
gateways, err := svc.GetGateways(ctx)
18+
if err != nil {
19+
return nil, err
20+
}
21+
return func(arg string) (uuid string, err error) {
22+
rv := ""
23+
for _, gtw := range gateways {
24+
if MatchArgWithWhitespace(arg, gtw.Name) || gtw.UUID == arg {
25+
if rv != "" {
26+
return "", AmbiguousResolutionError(arg)
27+
}
28+
rv = gtw.UUID
29+
}
30+
}
31+
if rv != "" {
32+
return rv, nil
33+
}
34+
return "", NotFoundError(arg)
35+
}, nil
36+
}
37+
38+
// PositionalArgumentHelp implements resolver.ResolutionProvider
39+
func (s CachingGateway) PositionalArgumentHelp() string {
40+
return helpUUIDTitle
41+
}

0 commit comments

Comments
 (0)