Skip to content

Commit 289cc47

Browse files
authored
feat(server): improve private cloud host selection support (#307)
1 parent c1f44cd commit 289cc47

12 files changed

Lines changed: 152 additions & 3 deletions

File tree

CHANGELOG.md

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

88
## [Unreleased]
99

10+
### Added
11+
12+
- Add `host list` command for listing private cloud hosts.
13+
- Add `--host` argument to `server restart` command.
14+
- Add `--avoid-host` and `--host` arguments to `server start` command.
15+
1016
### Changed
1117

1218
- Go version bump to 1.21

internal/commands/all/all.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
databaseproperties "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/database/properties"
1010
databasesession "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/database/session"
1111
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/gateway"
12+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/host"
1213
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/ipaddress"
1314
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/kubernetes"
1415
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/kubernetes/nodegroup"
@@ -202,6 +203,10 @@ func BuildCommands(rootCmd *cobra.Command, conf *config.Config) {
202203
commands.BuildCommand(gateway.DeleteCommand(), gatewayCommand.Cobra(), conf)
203204
commands.BuildCommand(gateway.ListCommand(), gatewayCommand.Cobra(), conf)
204205

206+
// Host operations
207+
hostCommand := commands.BuildCommand(host.BaseHostCommand(), rootCmd, conf)
208+
commands.BuildCommand(host.ListCommand(), hostCommand.Cobra(), conf)
209+
205210
// Misc
206211
commands.BuildCommand(
207212
&root.VersionCommand{

internal/commands/host/host.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package host
2+
3+
import (
4+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands"
5+
)
6+
7+
// BaseHostCommand creates the base "host" command
8+
func BaseHostCommand() commands.Command {
9+
return &hostCommand{
10+
commands.New("host", "Manage private cloud hosts"),
11+
}
12+
}
13+
14+
type hostCommand struct {
15+
*commands.BaseCommand
16+
}

internal/commands/host/list.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package host
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands"
7+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/format"
8+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/output"
9+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/ui"
10+
"github.com/jedib0t/go-pretty/v6/text"
11+
)
12+
13+
// ListCommand creates the "host list" command
14+
func ListCommand() commands.Command {
15+
return &listCommand{
16+
BaseCommand: commands.New("list", "List private cloud hosts", "upctl host list"),
17+
}
18+
}
19+
20+
type listCommand struct {
21+
*commands.BaseCommand
22+
}
23+
24+
// ExecuteWithoutArguments implements commands.NoArgumentCommand
25+
func (s *listCommand) ExecuteWithoutArguments(exec commands.Executor) (output.Output, error) {
26+
svc := exec.All()
27+
hosts, err := svc.GetHosts(exec.Context())
28+
if err != nil {
29+
return nil, err
30+
}
31+
32+
rows := []output.TableRow{}
33+
for _, h := range hosts.Hosts {
34+
rows = append(rows, output.TableRow{
35+
h.ID,
36+
h.Description,
37+
h.Zone,
38+
h.WindowsEnabled,
39+
})
40+
}
41+
42+
return output.MarshaledWithHumanOutput{
43+
Value: hosts,
44+
Output: output.Table{
45+
Columns: []output.TableColumn{
46+
{Key: "id", Header: "ID", Format: formatID},
47+
{Key: "description", Header: "Description"},
48+
{Key: "zone", Header: "Zone"},
49+
{Key: "windows_enabled", Header: "Windows enabled", Format: format.Boolean},
50+
},
51+
Rows: rows,
52+
},
53+
}, nil
54+
}
55+
56+
func formatID(val interface{}) (text.Colors, string, error) {
57+
id, ok := val.(int)
58+
if !ok {
59+
return nil, "", fmt.Errorf("cannot parse IP addresses from %T, expected int", val)
60+
}
61+
62+
return ui.DefaultUUUIDColours, fmt.Sprintf("%d", id), nil
63+
}

internal/commands/server/create.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,14 +256,14 @@ func (s *createCommand) InitCommand() {
256256
fs := &pflag.FlagSet{}
257257
s.params = createParams{CreateServerRequest: request.CreateServerRequest{}}
258258
def := defaultCreateParams
259-
fs.IntVar(&s.params.AvoidHost, "avoid-host", def.AvoidHost, "Use this to make sure VMs do not reside on specific host. Refers to value from host -attribute. Useful when building HA-environments.")
259+
fs.IntVar(&s.params.AvoidHost, "avoid-host", def.AvoidHost, avoidHostDescription)
260260
fs.StringVar(&s.params.BootOrder, "boot-order", def.BootOrder, "The boot device order, disk / cdrom / network or comma separated combination.")
261261
fs.IntVar(&s.params.CoreNumber, "cores", def.CoreNumber, "Number of cores. Only allowed if `plan` option is set to \"custom\".")
262262
config.AddToggleFlag(fs, &s.createPassword, "create-password", def.createPassword, "Create an admin password.")
263263
config.AddEnableOrDisableFlag(fs, &s.firewall, def.firewall, "firewall", "firewall")
264264
config.AddEnableOrDisableFlag(fs, &s.metadata, def.metadata, "metadata", "metadata service")
265265
config.AddEnableOrDisableFlag(fs, &s.remoteAccess, def.remoteAccess, "remote-access", "remote access")
266-
fs.IntVar(&s.params.Host, "host", def.Host, "Use this to start a VM on a specific private cloud host. Refers to value from host -attribute. Only available in private clouds.")
266+
fs.IntVar(&s.params.Host, "host", def.Host, hostDescription)
267267
fs.StringVar(&s.params.Hostname, "hostname", def.Hostname, "Server hostname.")
268268
fs.StringArrayVar(&s.params.labels, "label", def.labels, "Labels to describe the server in `key=value` format, multiple can be declared.\nUsage: --label env=dev\n\n--label owner=operations")
269269
fs.IntVar(&s.params.MemoryAmount, "memory", def.MemoryAmount, "Memory amount in MiB. Only allowed if `plan` option is set to \"custom\".")

internal/commands/server/restart.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type restartCommand struct {
3232
completion.Server
3333
WaitForServerToStart bool
3434
StopType string
35+
Host int
3536
TimeoutAction string
3637
Timeout time.Duration
3738
}
@@ -43,6 +44,7 @@ func (s *restartCommand) InitCommand() {
4344
// TODO: reimplement? does not seem to make sense to automagically destroy
4445
// servers if restart fails..
4546
flags.StringVar(&s.StopType, "stop-type", defaultStopType, "The type of stop operation. Available: soft, hard")
47+
flags.IntVar(&s.Host, "host", 0, hostDescription)
4648
s.AddFlags(flags)
4749
}
4850

@@ -60,6 +62,7 @@ func (s *restartCommand) Execute(exec commands.Executor, uuid string) (output.Ou
6062
res, err := svc.RestartServer(exec.Context(), &request.RestartServerRequest{
6163
UUID: uuid,
6264
StopType: s.StopType,
65+
Host: s.Host,
6366
Timeout: defaultRestartTimeout,
6467
TimeoutAction: "ignore",
6568
})

internal/commands/server/restart_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,19 @@ func TestRestartCommand(t *testing.T) {
6868
TimeoutAction: defaultRestartTimeoutAction,
6969
},
7070
},
71+
{
72+
name: "specify host",
73+
args: []string{
74+
"--host", "123456",
75+
},
76+
restartReq: request.RestartServerRequest{
77+
UUID: Server1.UUID,
78+
Host: 123456,
79+
StopType: defaultStopType,
80+
Timeout: defaultRestartTimeout,
81+
TimeoutAction: defaultRestartTimeoutAction,
82+
},
83+
},
7184
} {
7285
t.Run(test.name, func(t *testing.T) {
7386
conf := config.New()

internal/commands/server/server.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ const (
1818
defaultRestartTimeout = time.Duration(120) * time.Second
1919
defaultRestartTimeoutAction = request.RestartTimeoutActionIgnore
2020
customPlan = "custom"
21+
22+
avoidHostDescription = "Host to avoid when scheduling the server. Use this to make sure VMs do not reside on specific host. Refers to value from `host` attribute. Useful when building HA-environments."
23+
hostDescription = "Schedule the server on a specific host. Refers to value from `host` attribute. Only available in private clouds."
2124
)
2225

2326
// BaseServerCommand crestes the base "server" command

internal/commands/server/start.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/UpCloudLtd/upcloud-cli/v3/internal/completion"
88
"github.com/UpCloudLtd/upcloud-cli/v3/internal/output"
99
"github.com/UpCloudLtd/upcloud-cli/v3/internal/resolver"
10+
"github.com/spf13/pflag"
1011

1112
"github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request"
1213
)
@@ -28,10 +29,18 @@ type startCommand struct {
2829
*commands.BaseCommand
2930
completion.Server
3031
resolver.CachingServer
32+
host int
33+
avoidHost int
3134
}
3235

3336
// InitCommand implements Command.InitCommand
3437
func (s *startCommand) InitCommand() {
38+
fs := &pflag.FlagSet{}
39+
40+
fs.IntVar(&s.avoidHost, "avoid-host", 0, avoidHostDescription)
41+
fs.IntVar(&s.host, "host", 0, hostDescription)
42+
43+
s.AddFlags(fs)
3544
}
3645

3746
// Execute implements commands.MultipleArgumentCommand
@@ -41,7 +50,9 @@ func (s *startCommand) Execute(exec commands.Executor, uuid string) (output.Outp
4150
exec.PushProgressStarted(msg)
4251

4352
res, err := svc.StartServer(exec.Context(), &request.StartServerRequest{
44-
UUID: uuid,
53+
UUID: uuid,
54+
AvoidHost: s.avoidHost,
55+
Host: s.host,
4556
})
4657
if err != nil {
4758
return commands.HandleError(exec, msg, err)

internal/commands/server/start_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,22 @@ func TestStartCommand(t *testing.T) {
5252
UUID: Server1.UUID,
5353
},
5454
},
55+
{
56+
name: "host argument",
57+
args: []string{"--host", "123456"},
58+
startReq: request.StartServerRequest{
59+
UUID: Server1.UUID,
60+
Host: 123456,
61+
},
62+
},
63+
{
64+
name: "avoid-host argument",
65+
args: []string{"--avoid-host", "987654"},
66+
startReq: request.StartServerRequest{
67+
UUID: Server1.UUID,
68+
AvoidHost: 987654,
69+
},
70+
},
5571
} {
5672
t.Run(test.name, func(t *testing.T) {
5773
conf := config.New()

0 commit comments

Comments
 (0)