From 53742a903640780ab5917a465abf72e541d17062 Mon Sep 17 00:00:00 2001 From: Toni Kangas Date: Thu, 30 Jun 2022 19:21:03 +0300 Subject: [PATCH 1/2] feat(db): add database connection list and database connection cancel commands --- CHANGELOG.md | 1 + internal/commands/all/all.go | 6 ++ .../commands/database/connection/cancel.go | 67 ++++++++++++++++++ .../database/connection/cancel_test.go | 70 +++++++++++++++++++ .../database/connection/connection.go | 16 +++++ internal/commands/database/connection/list.go | 59 ++++++++++++++++ internal/mock/mock.go | 4 ++ 7 files changed, 223 insertions(+) create mode 100644 internal/commands/database/connection/cancel.go create mode 100644 internal/commands/database/connection/cancel_test.go create mode 100644 internal/commands/database/connection/connection.go create mode 100644 internal/commands/database/connection/list.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 61d6a59a5..780c89d1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added - Add `--show-ip-addresses` flag to `server list` command to optionally include IP addresses in command output. +- Add `database connection list`, and `database connection cancel` commands. ### Changed - Make `--family` parameter of `server firewall create` command optional to allow editing the default rules. diff --git a/internal/commands/all/all.go b/internal/commands/all/all.go index 3cff63306..0e57cb238 100644 --- a/internal/commands/all/all.go +++ b/internal/commands/all/all.go @@ -4,6 +4,7 @@ import ( "github.com/UpCloudLtd/upcloud-cli/internal/commands" "github.com/UpCloudLtd/upcloud-cli/internal/commands/account" "github.com/UpCloudLtd/upcloud-cli/internal/commands/database" + databaseconnection "github.com/UpCloudLtd/upcloud-cli/internal/commands/database/connection" "github.com/UpCloudLtd/upcloud-cli/internal/commands/ipaddress" "github.com/UpCloudLtd/upcloud-cli/internal/commands/loadbalancer" "github.com/UpCloudLtd/upcloud-cli/internal/commands/network" @@ -107,6 +108,11 @@ func BuildCommands(rootCmd *cobra.Command, conf *config.Config) { commands.BuildCommand(database.TypesCommand(), databaseCommand.Cobra(), conf) commands.BuildCommand(database.PlansCommand(), databaseCommand.Cobra(), conf) + // Database connections + connectionsCommand := commands.BuildCommand(databaseconnection.BaseConnectionCommand(), databaseCommand.Cobra(), conf) + commands.BuildCommand(databaseconnection.CancelCommand(), connectionsCommand.Cobra(), conf) + commands.BuildCommand(databaseconnection.ListCommand(), connectionsCommand.Cobra(), conf) + // LoadBalancers loadbalancerCommand := commands.BuildCommand(loadbalancer.BaseLoadBalancerCommand(), rootCmd, conf) commands.BuildCommand(loadbalancer.ListCommand(), loadbalancerCommand.Cobra(), conf) diff --git a/internal/commands/database/connection/cancel.go b/internal/commands/database/connection/cancel.go new file mode 100644 index 000000000..f712507f0 --- /dev/null +++ b/internal/commands/database/connection/cancel.go @@ -0,0 +1,67 @@ +package databaseconnection + +import ( + "fmt" + + "github.com/UpCloudLtd/upcloud-cli/internal/commands" + "github.com/UpCloudLtd/upcloud-cli/internal/completion" + "github.com/UpCloudLtd/upcloud-cli/internal/config" + "github.com/UpCloudLtd/upcloud-cli/internal/output" + "github.com/UpCloudLtd/upcloud-cli/internal/resolver" + + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/request" + "github.com/spf13/pflag" +) + +type cancelCommand struct { + *commands.BaseCommand + resolver.CachingDatabase + completion.Database + pid int + terminate config.OptionalBoolean +} + +// CancelCommand creates the "connection cancel" command +func CancelCommand() commands.Command { + return &cancelCommand{ + BaseCommand: commands.New( + "cancel", + "Terminate client connection or cancel runnig query for a database", + `upctl database connection cancel 0fa980c4-0e4f-460b-9869-11b7bd62b833 --pid 2345421`, + `upctl database connection cancel 0fa980c4-0e4f-460b-9869-11b7bd62b833 --pid 2345421 --terminate`, + ), + } +} + +// InitCommand implements Command.InitCommand +func (s *cancelCommand) InitCommand() { + flagSet := &pflag.FlagSet{} + flagSet.IntVar(&s.pid, "pid", 0, "Process ID of the connection to cancel.") + config.AddToggleFlag(flagSet, &s.terminate, "terminate", false, "Request immediate termination instead of soft cancel.") + + s.AddFlags(flagSet) + s.Cobra().MarkFlagRequired("pid") //nolint:errcheck +} + +// Execute implements commands.MultipleArgumentCommand +func (s *cancelCommand) Execute(exec commands.Executor, uuid string) (output.Output, error) { + svc := exec.All() + + msg := fmt.Sprintf("Cancelling connection %v to database %v", s.pid, uuid) + logline := exec.NewLogEntry(msg) + + logline.StartedNow() + + if err := svc.CancelManagedDatabaseConnection(&request.CancelManagedDatabaseConnection{ + UUID: uuid, + Pid: s.pid, + Terminate: s.terminate.Value(), + }); err != nil { + return commands.HandleError(logline, fmt.Sprintf("%s: failed", msg), err) + } + + logline.SetMessage(fmt.Sprintf("%s: success", msg)) + logline.MarkDone() + + return output.None{}, nil +} diff --git a/internal/commands/database/connection/cancel_test.go b/internal/commands/database/connection/cancel_test.go new file mode 100644 index 000000000..8d8094f18 --- /dev/null +++ b/internal/commands/database/connection/cancel_test.go @@ -0,0 +1,70 @@ +package databaseconnection + +import ( + "testing" + + "github.com/UpCloudLtd/upcloud-cli/internal/commands" + "github.com/UpCloudLtd/upcloud-cli/internal/config" + smock "github.com/UpCloudLtd/upcloud-cli/internal/mock" + "github.com/UpCloudLtd/upcloud-cli/internal/mockexecute" + internal "github.com/UpCloudLtd/upcloud-cli/internal/service" + + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/request" + "github.com/stretchr/testify/assert" +) + +func TestCreateCommand(t *testing.T) { + targetMethod := "CancelManagedDatabaseConnection" + uuid := "0fa980c4-0e4f-460b-9869-11b7bd62b833" + for _, test := range []struct { + name string + args []string + error string + expected request.CancelManagedDatabaseConnection + }{ + { + name: "no process id", + args: []string{}, + error: `required flag(s) "pid" not set`, + }, + { + name: "soft cancel", + args: []string{"--pid", "123456"}, + expected: request.CancelManagedDatabaseConnection{ + UUID: uuid, + Pid: 123456, + Terminate: false, + }, + }, + { + name: "terminate", + args: []string{"--pid", "987654", "--terminate"}, + expected: request.CancelManagedDatabaseConnection{ + UUID: uuid, + Pid: 987654, + Terminate: true, + }, + }, + } { + t.Run(test.name, func(t *testing.T) { + conf := config.New() + testCmd := CancelCommand() + mService := new(smock.Service) + + conf.Service = internal.Wrapper{Service: mService} + mService.On(targetMethod, &test.expected).Return(nil) + + c := commands.BuildCommand(testCmd, nil, config.New()) + + c.Cobra().SetArgs(append(test.args, uuid)) + _, err := mockexecute.MockExecute(c, mService, conf) + + if test.error != "" { + assert.EqualError(t, err, test.error) + } else { + assert.NoError(t, err) + mService.AssertNumberOfCalls(t, targetMethod, 1) + } + }) + } +} diff --git a/internal/commands/database/connection/connection.go b/internal/commands/database/connection/connection.go new file mode 100644 index 000000000..5c4072e34 --- /dev/null +++ b/internal/commands/database/connection/connection.go @@ -0,0 +1,16 @@ +package databaseconnection + +import ( + "github.com/UpCloudLtd/upcloud-cli/internal/commands" +) + +// BaseConnectionCommand creates the base "connection" command +func BaseConnectionCommand() commands.Command { + return &databaseConnectionCommand{ + commands.New("connection", "Manage database connections"), + } +} + +type databaseConnectionCommand struct { + *commands.BaseCommand +} diff --git a/internal/commands/database/connection/list.go b/internal/commands/database/connection/list.go new file mode 100644 index 000000000..9ea15e4bb --- /dev/null +++ b/internal/commands/database/connection/list.go @@ -0,0 +1,59 @@ +package databaseconnection + +import ( + "github.com/UpCloudLtd/upcloud-cli/internal/commands" + "github.com/UpCloudLtd/upcloud-cli/internal/completion" + "github.com/UpCloudLtd/upcloud-cli/internal/output" + "github.com/UpCloudLtd/upcloud-cli/internal/resolver" + "github.com/UpCloudLtd/upcloud-cli/internal/ui" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/request" +) + +// ListCommand creates the "connection list" command +func ListCommand() commands.Command { + return &listCommand{ + BaseCommand: commands.New("list", "List current connections to specified databases", "upctl database connection list"), + } +} + +type listCommand struct { + *commands.BaseCommand + resolver.CachingDatabase + completion.Database +} + +// Execute implements commands.MultipleArgumentCommand +func (s *listCommand) Execute(exec commands.Executor, uuid string) (output.Output, error) { + svc := exec.All() + connections, err := svc.GetManagedDatabaseConnections(&request.GetManagedDatabaseConnectionsRequest{UUID: uuid}) + if err != nil { + return nil, err + } + + rows := []output.TableRow{} + for _, conn := range connections { + rows = append(rows, output.TableRow{ + conn.Pid, + conn.State, + conn.ApplicationName, + conn.DatName, + conn.Username, + conn.ClientAddr, + }) + } + + return output.MarshaledWithHumanOutput{ + Value: connections, + Output: output.Table{ + Columns: []output.TableColumn{ + {Key: "pid", Header: "Process ID"}, + {Key: "state", Header: "State"}, + {Key: "application_name", Header: "Application name"}, + {Key: "dataname", Header: "Database"}, + {Key: "username", Header: "Username"}, + {Key: "client_addr", Header: "Client IP", Colour: ui.DefaultAddressColours}, + }, + Rows: rows, + }, + }, nil +} diff --git a/internal/mock/mock.go b/internal/mock/mock.go index 0a7c71a10..877aeabdd 100644 --- a/internal/mock/mock.go +++ b/internal/mock/mock.go @@ -559,6 +559,10 @@ func (m *Service) DeleteSubaccount(r *request.DeleteSubaccountRequest) error { } func (m *Service) CancelManagedDatabaseConnection(r *request.CancelManagedDatabaseConnection) error { + args := m.Called(r) + if args[0] != nil { + return args.Error(0) + } return nil } From 609d6390e796f515a3b235dff8e354657a9935a3 Mon Sep 17 00:00:00 2001 From: Toni Kangas Date: Fri, 1 Jul 2022 13:24:05 +0300 Subject: [PATCH 2/2] refactor(server): move server subcommand packages under server packages This syncs directory structure with command structure. --- internal/commands/all/all.go | 6 +++--- .../commands/{serverfirewall => server/firewall}/create.go | 0 .../{serverfirewall => server/firewall}/create_test.go | 0 .../commands/{serverfirewall => server/firewall}/delete.go | 0 .../{serverfirewall => server/firewall}/delete_test.go | 0 .../{serverfirewall => server/firewall}/server_firewall.go | 0 .../commands/{serverfirewall => server/firewall}/show.go | 0 .../{serverfirewall => server/firewall}/show_test.go | 0 internal/commands/{ => server}/networkinterface/create.go | 0 .../commands/{ => server}/networkinterface/create_test.go | 0 internal/commands/{ => server}/networkinterface/delete.go | 0 .../commands/{ => server}/networkinterface/delete_test.go | 0 internal/commands/{ => server}/networkinterface/modify.go | 0 .../commands/{ => server}/networkinterface/modify_test.go | 0 .../{ => server}/networkinterface/network_interface.go | 0 .../commands/{serverstorage => server/storage}/attach.go | 0 .../{serverstorage => server/storage}/attach_test.go | 0 .../commands/{serverstorage => server/storage}/detach.go | 0 .../{serverstorage => server/storage}/detach_test.go | 0 .../{serverstorage => server/storage}/server_storage.go | 0 .../storage}/server_storage_test.go | 0 21 files changed, 3 insertions(+), 3 deletions(-) rename internal/commands/{serverfirewall => server/firewall}/create.go (100%) rename internal/commands/{serverfirewall => server/firewall}/create_test.go (100%) rename internal/commands/{serverfirewall => server/firewall}/delete.go (100%) rename internal/commands/{serverfirewall => server/firewall}/delete_test.go (100%) rename internal/commands/{serverfirewall => server/firewall}/server_firewall.go (100%) rename internal/commands/{serverfirewall => server/firewall}/show.go (100%) rename internal/commands/{serverfirewall => server/firewall}/show_test.go (100%) rename internal/commands/{ => server}/networkinterface/create.go (100%) rename internal/commands/{ => server}/networkinterface/create_test.go (100%) rename internal/commands/{ => server}/networkinterface/delete.go (100%) rename internal/commands/{ => server}/networkinterface/delete_test.go (100%) rename internal/commands/{ => server}/networkinterface/modify.go (100%) rename internal/commands/{ => server}/networkinterface/modify_test.go (100%) rename internal/commands/{ => server}/networkinterface/network_interface.go (100%) rename internal/commands/{serverstorage => server/storage}/attach.go (100%) rename internal/commands/{serverstorage => server/storage}/attach_test.go (100%) rename internal/commands/{serverstorage => server/storage}/detach.go (100%) rename internal/commands/{serverstorage => server/storage}/detach_test.go (100%) rename internal/commands/{serverstorage => server/storage}/server_storage.go (100%) rename internal/commands/{serverstorage => server/storage}/server_storage_test.go (100%) diff --git a/internal/commands/all/all.go b/internal/commands/all/all.go index 0e57cb238..5ceb00b5f 100644 --- a/internal/commands/all/all.go +++ b/internal/commands/all/all.go @@ -8,12 +8,12 @@ import ( "github.com/UpCloudLtd/upcloud-cli/internal/commands/ipaddress" "github.com/UpCloudLtd/upcloud-cli/internal/commands/loadbalancer" "github.com/UpCloudLtd/upcloud-cli/internal/commands/network" - "github.com/UpCloudLtd/upcloud-cli/internal/commands/networkinterface" "github.com/UpCloudLtd/upcloud-cli/internal/commands/root" "github.com/UpCloudLtd/upcloud-cli/internal/commands/router" "github.com/UpCloudLtd/upcloud-cli/internal/commands/server" - "github.com/UpCloudLtd/upcloud-cli/internal/commands/serverfirewall" - "github.com/UpCloudLtd/upcloud-cli/internal/commands/serverstorage" + serverfirewall "github.com/UpCloudLtd/upcloud-cli/internal/commands/server/firewall" + "github.com/UpCloudLtd/upcloud-cli/internal/commands/server/networkinterface" + serverstorage "github.com/UpCloudLtd/upcloud-cli/internal/commands/server/storage" "github.com/UpCloudLtd/upcloud-cli/internal/commands/storage" "github.com/UpCloudLtd/upcloud-cli/internal/commands/zone" "github.com/UpCloudLtd/upcloud-cli/internal/config" diff --git a/internal/commands/serverfirewall/create.go b/internal/commands/server/firewall/create.go similarity index 100% rename from internal/commands/serverfirewall/create.go rename to internal/commands/server/firewall/create.go diff --git a/internal/commands/serverfirewall/create_test.go b/internal/commands/server/firewall/create_test.go similarity index 100% rename from internal/commands/serverfirewall/create_test.go rename to internal/commands/server/firewall/create_test.go diff --git a/internal/commands/serverfirewall/delete.go b/internal/commands/server/firewall/delete.go similarity index 100% rename from internal/commands/serverfirewall/delete.go rename to internal/commands/server/firewall/delete.go diff --git a/internal/commands/serverfirewall/delete_test.go b/internal/commands/server/firewall/delete_test.go similarity index 100% rename from internal/commands/serverfirewall/delete_test.go rename to internal/commands/server/firewall/delete_test.go diff --git a/internal/commands/serverfirewall/server_firewall.go b/internal/commands/server/firewall/server_firewall.go similarity index 100% rename from internal/commands/serverfirewall/server_firewall.go rename to internal/commands/server/firewall/server_firewall.go diff --git a/internal/commands/serverfirewall/show.go b/internal/commands/server/firewall/show.go similarity index 100% rename from internal/commands/serverfirewall/show.go rename to internal/commands/server/firewall/show.go diff --git a/internal/commands/serverfirewall/show_test.go b/internal/commands/server/firewall/show_test.go similarity index 100% rename from internal/commands/serverfirewall/show_test.go rename to internal/commands/server/firewall/show_test.go diff --git a/internal/commands/networkinterface/create.go b/internal/commands/server/networkinterface/create.go similarity index 100% rename from internal/commands/networkinterface/create.go rename to internal/commands/server/networkinterface/create.go diff --git a/internal/commands/networkinterface/create_test.go b/internal/commands/server/networkinterface/create_test.go similarity index 100% rename from internal/commands/networkinterface/create_test.go rename to internal/commands/server/networkinterface/create_test.go diff --git a/internal/commands/networkinterface/delete.go b/internal/commands/server/networkinterface/delete.go similarity index 100% rename from internal/commands/networkinterface/delete.go rename to internal/commands/server/networkinterface/delete.go diff --git a/internal/commands/networkinterface/delete_test.go b/internal/commands/server/networkinterface/delete_test.go similarity index 100% rename from internal/commands/networkinterface/delete_test.go rename to internal/commands/server/networkinterface/delete_test.go diff --git a/internal/commands/networkinterface/modify.go b/internal/commands/server/networkinterface/modify.go similarity index 100% rename from internal/commands/networkinterface/modify.go rename to internal/commands/server/networkinterface/modify.go diff --git a/internal/commands/networkinterface/modify_test.go b/internal/commands/server/networkinterface/modify_test.go similarity index 100% rename from internal/commands/networkinterface/modify_test.go rename to internal/commands/server/networkinterface/modify_test.go diff --git a/internal/commands/networkinterface/network_interface.go b/internal/commands/server/networkinterface/network_interface.go similarity index 100% rename from internal/commands/networkinterface/network_interface.go rename to internal/commands/server/networkinterface/network_interface.go diff --git a/internal/commands/serverstorage/attach.go b/internal/commands/server/storage/attach.go similarity index 100% rename from internal/commands/serverstorage/attach.go rename to internal/commands/server/storage/attach.go diff --git a/internal/commands/serverstorage/attach_test.go b/internal/commands/server/storage/attach_test.go similarity index 100% rename from internal/commands/serverstorage/attach_test.go rename to internal/commands/server/storage/attach_test.go diff --git a/internal/commands/serverstorage/detach.go b/internal/commands/server/storage/detach.go similarity index 100% rename from internal/commands/serverstorage/detach.go rename to internal/commands/server/storage/detach.go diff --git a/internal/commands/serverstorage/detach_test.go b/internal/commands/server/storage/detach_test.go similarity index 100% rename from internal/commands/serverstorage/detach_test.go rename to internal/commands/server/storage/detach_test.go diff --git a/internal/commands/serverstorage/server_storage.go b/internal/commands/server/storage/server_storage.go similarity index 100% rename from internal/commands/serverstorage/server_storage.go rename to internal/commands/server/storage/server_storage.go diff --git a/internal/commands/serverstorage/server_storage_test.go b/internal/commands/server/storage/server_storage_test.go similarity index 100% rename from internal/commands/serverstorage/server_storage_test.go rename to internal/commands/server/storage/server_storage_test.go