Skip to content

Commit ddc1e9d

Browse files
committed
feat(db): add database connection list and database connection cancel commands
1 parent 17e3ab4 commit ddc1e9d

File tree

7 files changed

+223
-0
lines changed

7 files changed

+223
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99
### Added
1010
- Add `--show-ip-addresses` flag to `server list` command to optionally include IP addresses in command output.
11+
- Add `database connection list`, and `database connection cancel` commands.
1112

1213
### Changed
1314
- Make `--family` parameter of `server firewall create` command optional to allow editing the default rules.

internal/commands/all/all.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"github.com/UpCloudLtd/upcloud-cli/internal/commands"
55
"github.com/UpCloudLtd/upcloud-cli/internal/commands/account"
66
"github.com/UpCloudLtd/upcloud-cli/internal/commands/database"
7+
databaseconnection "github.com/UpCloudLtd/upcloud-cli/internal/commands/database/connection"
78
"github.com/UpCloudLtd/upcloud-cli/internal/commands/ipaddress"
89
"github.com/UpCloudLtd/upcloud-cli/internal/commands/loadbalancer"
910
"github.com/UpCloudLtd/upcloud-cli/internal/commands/network"
@@ -107,6 +108,11 @@ func BuildCommands(rootCmd *cobra.Command, conf *config.Config) {
107108
commands.BuildCommand(database.TypesCommand(), databaseCommand.Cobra(), conf)
108109
commands.BuildCommand(database.PlansCommand(), databaseCommand.Cobra(), conf)
109110

111+
// Database connections
112+
connectionsCommand := commands.BuildCommand(databaseconnection.BaseConnectionCommand(), databaseCommand.Cobra(), conf)
113+
commands.BuildCommand(databaseconnection.CancelCommand(), connectionsCommand.Cobra(), conf)
114+
commands.BuildCommand(databaseconnection.ListCommand(), connectionsCommand.Cobra(), conf)
115+
110116
// LoadBalancers
111117
loadbalancerCommand := commands.BuildCommand(loadbalancer.BaseLoadBalancerCommand(), rootCmd, conf)
112118
commands.BuildCommand(loadbalancer.ListCommand(), loadbalancerCommand.Cobra(), conf)
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package databaseconnection
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/UpCloudLtd/upcloud-cli/internal/commands"
7+
"github.com/UpCloudLtd/upcloud-cli/internal/completion"
8+
"github.com/UpCloudLtd/upcloud-cli/internal/config"
9+
"github.com/UpCloudLtd/upcloud-cli/internal/output"
10+
"github.com/UpCloudLtd/upcloud-cli/internal/resolver"
11+
12+
"github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/request"
13+
"github.com/spf13/pflag"
14+
)
15+
16+
type cancelCommand struct {
17+
*commands.BaseCommand
18+
resolver.CachingDatabase
19+
completion.Database
20+
pid int
21+
terminate config.OptionalBoolean
22+
}
23+
24+
// CancelCommand creates the "connection cancel" command
25+
func CancelCommand() commands.Command {
26+
return &cancelCommand{
27+
BaseCommand: commands.New(
28+
"cancel",
29+
"Terminate client connection or cancel runnig query for a database",
30+
`upctl database connection cancel 0fa980c4-0e4f-460b-9869-11b7bd62b833 --pid 2345421`,
31+
`upctl database connection cancel 0fa980c4-0e4f-460b-9869-11b7bd62b833 --pid 2345421 --terminate`,
32+
),
33+
}
34+
}
35+
36+
// InitCommand implements Command.InitCommand
37+
func (s *cancelCommand) InitCommand() {
38+
flagSet := &pflag.FlagSet{}
39+
flagSet.IntVar(&s.pid, "pid", 0, "Process ID of the connection to cancel.")
40+
config.AddToggleFlag(flagSet, &s.terminate, "terminate", false, "Request immediate termination instead of soft cancel.")
41+
42+
s.AddFlags(flagSet)
43+
s.Cobra().MarkFlagRequired("pid") //nolint:errcheck
44+
}
45+
46+
// Execute implements commands.MultipleArgumentCommand
47+
func (s *cancelCommand) Execute(exec commands.Executor, uuid string) (output.Output, error) {
48+
svc := exec.All()
49+
50+
msg := fmt.Sprintf("Cancelling connection %v to database %v", s.pid, uuid)
51+
logline := exec.NewLogEntry(msg)
52+
53+
logline.StartedNow()
54+
55+
if err := svc.CancelManagedDatabaseConnection(&request.CancelManagedDatabaseConnection{
56+
UUID: uuid,
57+
Pid: s.pid,
58+
Terminate: s.terminate.Value(),
59+
}); err != nil {
60+
return commands.HandleError(logline, fmt.Sprintf("%s: failed", msg), err)
61+
}
62+
63+
logline.SetMessage(fmt.Sprintf("%s: success", msg))
64+
logline.MarkDone()
65+
66+
return output.None{}, nil
67+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package databaseconnection
2+
3+
import (
4+
"testing"
5+
6+
"github.com/UpCloudLtd/upcloud-cli/internal/commands"
7+
"github.com/UpCloudLtd/upcloud-cli/internal/config"
8+
smock "github.com/UpCloudLtd/upcloud-cli/internal/mock"
9+
"github.com/UpCloudLtd/upcloud-cli/internal/mockexecute"
10+
internal "github.com/UpCloudLtd/upcloud-cli/internal/service"
11+
12+
"github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/request"
13+
"github.com/stretchr/testify/assert"
14+
)
15+
16+
func TestCreateCommand(t *testing.T) {
17+
targetMethod := "CancelManagedDatabaseConnection"
18+
uuid := "0fa980c4-0e4f-460b-9869-11b7bd62b833"
19+
for _, test := range []struct {
20+
name string
21+
args []string
22+
error string
23+
expected request.CancelManagedDatabaseConnection
24+
}{
25+
{
26+
name: "no process id",
27+
args: []string{},
28+
error: `required flag(s) "pid" not set`,
29+
},
30+
{
31+
name: "soft cancel",
32+
args: []string{"--pid", "123456"},
33+
expected: request.CancelManagedDatabaseConnection{
34+
UUID: uuid,
35+
Pid: 123456,
36+
Terminate: false,
37+
},
38+
},
39+
{
40+
name: "terminate",
41+
args: []string{"--pid", "987654", "--terminate"},
42+
expected: request.CancelManagedDatabaseConnection{
43+
UUID: uuid,
44+
Pid: 987654,
45+
Terminate: true,
46+
},
47+
},
48+
} {
49+
t.Run(test.name, func(t *testing.T) {
50+
conf := config.New()
51+
testCmd := CancelCommand()
52+
mService := new(smock.Service)
53+
54+
conf.Service = internal.Wrapper{Service: mService}
55+
mService.On(targetMethod, &test.expected).Return(nil)
56+
57+
c := commands.BuildCommand(testCmd, nil, config.New())
58+
59+
c.Cobra().SetArgs(append(test.args, uuid))
60+
_, err := mockexecute.MockExecute(c, mService, conf)
61+
62+
if test.error != "" {
63+
assert.EqualError(t, err, test.error)
64+
} else {
65+
assert.NoError(t, err)
66+
mService.AssertNumberOfCalls(t, targetMethod, 1)
67+
}
68+
})
69+
}
70+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package databaseconnection
2+
3+
import (
4+
"github.com/UpCloudLtd/upcloud-cli/internal/commands"
5+
)
6+
7+
// BaseConnectionCommand creates the base "connection" command
8+
func BaseConnectionCommand() commands.Command {
9+
return &databaseConnectionCommand{
10+
commands.New("connection", "Manage database connections"),
11+
}
12+
}
13+
14+
type databaseConnectionCommand struct {
15+
*commands.BaseCommand
16+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package databaseconnection
2+
3+
import (
4+
"github.com/UpCloudLtd/upcloud-cli/internal/commands"
5+
"github.com/UpCloudLtd/upcloud-cli/internal/completion"
6+
"github.com/UpCloudLtd/upcloud-cli/internal/output"
7+
"github.com/UpCloudLtd/upcloud-cli/internal/resolver"
8+
"github.com/UpCloudLtd/upcloud-cli/internal/ui"
9+
"github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/request"
10+
)
11+
12+
// ListCommand creates the "connection list" command
13+
func ListCommand() commands.Command {
14+
return &listCommand{
15+
BaseCommand: commands.New("list", "List current connections to specified databases", "upctl database connection list"),
16+
}
17+
}
18+
19+
type listCommand struct {
20+
*commands.BaseCommand
21+
resolver.CachingDatabase
22+
completion.Database
23+
}
24+
25+
// Execute implements commands.MultipleArgumentCommand
26+
func (s *listCommand) Execute(exec commands.Executor, uuid string) (output.Output, error) {
27+
svc := exec.All()
28+
connections, err := svc.GetManagedDatabaseConnections(&request.GetManagedDatabaseConnectionsRequest{UUID: uuid})
29+
if err != nil {
30+
return nil, err
31+
}
32+
33+
rows := []output.TableRow{}
34+
for _, conn := range connections {
35+
rows = append(rows, output.TableRow{
36+
conn.Pid,
37+
conn.State,
38+
conn.ApplicationName,
39+
conn.DatName,
40+
conn.Username,
41+
conn.ClientAddr,
42+
})
43+
}
44+
45+
return output.MarshaledWithHumanOutput{
46+
Value: connections,
47+
Output: output.Table{
48+
Columns: []output.TableColumn{
49+
{Key: "pid", Header: "Process ID"},
50+
{Key: "state", Header: "State"},
51+
{Key: "application_name", Header: "Application name"},
52+
{Key: "dataname", Header: "Database"},
53+
{Key: "username", Header: "Username"},
54+
{Key: "client_addr", Header: "Client IP", Colour: ui.DefaultAddressColours},
55+
},
56+
Rows: rows,
57+
},
58+
}, nil
59+
}

internal/mock/mock.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,10 @@ func (m *Service) DeleteSubaccount(r *request.DeleteSubaccountRequest) error {
559559
}
560560

561561
func (m *Service) CancelManagedDatabaseConnection(r *request.CancelManagedDatabaseConnection) error {
562+
args := m.Called(r)
563+
if args[0] != nil {
564+
return args.Error(0)
565+
}
562566
return nil
563567
}
564568

0 commit comments

Comments
 (0)