Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
12 changes: 9 additions & 3 deletions internal/commands/all/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ 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"
"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"
Expand Down Expand Up @@ -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)
Expand Down
67 changes: 67 additions & 0 deletions internal/commands/database/connection/cancel.go
Original file line number Diff line number Diff line change
@@ -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
}
70 changes: 70 additions & 0 deletions internal/commands/database/connection/cancel_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}
16 changes: 16 additions & 0 deletions internal/commands/database/connection/connection.go
Original file line number Diff line number Diff line change
@@ -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
}
59 changes: 59 additions & 0 deletions internal/commands/database/connection/list.go
Original file line number Diff line number Diff line change
@@ -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
}
4 changes: 4 additions & 0 deletions internal/mock/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down