Skip to content

Commit c0bfcf5

Browse files
authored
feat(zone): add command for listing available devices (#469)
1 parent 80ed8de commit c0bfcf5

7 files changed

Lines changed: 150 additions & 3 deletions

File tree

CHANGELOG.md

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

88
## [Unreleased]
99

10+
### Added
11+
12+
- Add `zone devices` and `zone devices show` commands for listing available devices.
13+
1014
## [3.20.2] - 2025-07-09
1115

1216
### Changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ toolchain go1.24.2
66

77
require (
88
github.com/UpCloudLtd/progress v1.0.3
9-
github.com/UpCloudLtd/upcloud-go-api/v8 v8.19.0
9+
github.com/UpCloudLtd/upcloud-go-api/v8 v8.21.0
1010
github.com/adrg/xdg v0.3.2
1111
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
1212
github.com/gemalto/flume v0.12.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
1919
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
2020
github.com/UpCloudLtd/progress v1.0.3 h1:8SfntHkBPyQc5BL3946Bgi9KYnQOxa5RR2EKdadujdg=
2121
github.com/UpCloudLtd/progress v1.0.3/go.mod h1:iGxOnb9HvHW0yrLGUjHr0lxHhn7TehgWwh7a8NqK6iQ=
22-
github.com/UpCloudLtd/upcloud-go-api/v8 v8.19.0 h1:jw1PuZhalunYXiIWM4B+p3JNO6PAxZ4QYdJNHmq2CMs=
23-
github.com/UpCloudLtd/upcloud-go-api/v8 v8.19.0/go.mod h1:zA0iX1Kjf88t+q3AvOM5UD90iwIZabRH0qjQJ9b9GTc=
22+
github.com/UpCloudLtd/upcloud-go-api/v8 v8.21.0 h1:gPi3bLwNixV2xo3IFMhP65uOdJcE2KCooXQ5bzPJqEk=
23+
github.com/UpCloudLtd/upcloud-go-api/v8 v8.21.0/go.mod h1:ImDdnWfVVM6WCRTrskGhAw2W1cRwu5IEkBw+9UCzAv8=
2424
github.com/adrg/xdg v0.3.2 h1:GUSGQ5pHdev83AYhDSS1A/CX+0JIsxbiWtow2DSA+RU=
2525
github.com/adrg/xdg v0.3.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ=
2626
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=

internal/commands/base/base.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/storage"
3232
storagebackup "github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/storage/backup"
3333
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/zone"
34+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands/zone/devices"
3435
"github.com/UpCloudLtd/upcloud-cli/v3/internal/config"
3536

3637
"github.com/spf13/cobra"
@@ -136,6 +137,8 @@ func BuildCommands(rootCmd *cobra.Command, conf *config.Config) {
136137
// Zone
137138
zoneCommand := commands.BuildCommand(zone.BaseZoneCommand(), rootCmd, conf)
138139
commands.BuildCommand(zone.ListCommand(), zoneCommand.Cobra(), conf)
140+
devicesCommand := commands.BuildCommand(devices.DevicesCommand(), zoneCommand.Cobra(), conf)
141+
commands.BuildCommand(devices.ShowCommand(), devicesCommand.Cobra(), conf)
139142

140143
// Databases
141144
databaseCommand := commands.BuildCommand(database.BaseDatabaseCommand(), rootCmd, conf)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package devices
2+
3+
import (
4+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/commands"
5+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/format"
6+
"github.com/UpCloudLtd/upcloud-cli/v3/internal/output"
7+
)
8+
9+
// DevicesCommand creates the "zone devices" command
10+
func DevicesCommand() commands.Command { //nolint:revive // Does not make sense to add Base prefix here to avoid stuttering warning
11+
return &devicesCommand{
12+
BaseCommand: commands.New("devices", "List available devices for each zone", "upctl zone devices"),
13+
}
14+
}
15+
16+
type devicesCommand struct {
17+
*commands.BaseCommand
18+
}
19+
20+
// ExecuteWithoutArguments implements commands.NoArgumentCommand
21+
func (s *devicesCommand) ExecuteWithoutArguments(exec commands.Executor) (output.Output, error) {
22+
svc := exec.All()
23+
d, err := svc.GetDevicesAvailability(exec.Context())
24+
if err != nil {
25+
return nil, err
26+
}
27+
28+
rows := []output.TableRow{}
29+
for zone, devices := range *d {
30+
features := []string{}
31+
if len(devices.GPUPlans) > 0 {
32+
features = append(features, "GPU")
33+
}
34+
35+
rows = append(rows, output.TableRow{
36+
zone,
37+
features,
38+
})
39+
}
40+
41+
columns := []output.TableColumn{
42+
{Key: "zone", Header: "Zone"},
43+
{Key: "devices", Header: "Devices", Format: format.StringSliceSingleLineAnd},
44+
}
45+
46+
return output.MarshaledWithHumanOutput{
47+
Value: d,
48+
Output: output.Table{
49+
Columns: columns,
50+
Rows: rows,
51+
},
52+
}, nil
53+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package devices
2+
3+
import (
4+
"sort"
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+
)
10+
11+
// ShowCommand creates the "zone devices show" command
12+
func ShowCommand() commands.Command {
13+
return &showCommand{
14+
BaseCommand: commands.New("show", "Show detailed device availability for the given zone", "upctl zone devices show fi-hel2"),
15+
}
16+
}
17+
18+
type showCommand struct {
19+
*commands.BaseCommand
20+
completion.Zone
21+
}
22+
23+
// Execute implements commands.MultipleArgumentCommand
24+
func (s *showCommand) Execute(exec commands.Executor, zone string) (output.Output, error) {
25+
svc := exec.All()
26+
d, err := svc.GetDevicesAvailability(exec.Context())
27+
if err != nil {
28+
return nil, err
29+
}
30+
31+
sections := output.Combined{}
32+
33+
if len((*d)[zone].GPUPlans) > 0 {
34+
rows := []output.TableRow{}
35+
for name, data := range (*d)[zone].GPUPlans {
36+
rows = append(rows, output.TableRow{
37+
name,
38+
data.Amount,
39+
})
40+
}
41+
42+
sections = append(sections, output.CombinedSection{
43+
Key: "gpu_plans",
44+
Title: "GPU plans",
45+
Contents: output.Table{
46+
Columns: []output.TableColumn{
47+
{Key: "name", Header: "Name"},
48+
{Key: "amount", Header: "Amount"},
49+
},
50+
Rows: rows,
51+
},
52+
})
53+
54+
sort.Slice(rows, func(i, j int) bool {
55+
return rows[i][0].(string) < rows[j][0].(string)
56+
})
57+
}
58+
59+
return output.MarshaledWithHumanOutput{
60+
Value: (*d)[zone],
61+
Output: sections,
62+
}, nil
63+
}

internal/mock/mock.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1504,3 +1504,27 @@ func (m *Service) TagServer(ctx context.Context, r *request.TagServerRequest) (*
15041504
func (m *Service) UntagServer(ctx context.Context, r *request.UntagServerRequest) (*upcloud.ServerDetails, error) {
15051505
return nil, nil
15061506
}
1507+
1508+
func (m *Service) WaitForLoadBalancerOperationalState(_ context.Context, r *request.WaitForLoadBalancerOperationalStateRequest) (*upcloud.LoadBalancer, error) {
1509+
args := m.Called(r)
1510+
if args[0] == nil {
1511+
return nil, args.Error(1)
1512+
}
1513+
return args[0].(*upcloud.LoadBalancer), args.Error(1)
1514+
}
1515+
1516+
func (m *Service) WaitForLoadBalancerDeletion(_ context.Context, r *request.WaitForLoadBalancerDeletionRequest) error {
1517+
args := m.Called(r)
1518+
if args[0] == nil {
1519+
return args.Error(0)
1520+
}
1521+
return nil
1522+
}
1523+
1524+
func (m *Service) GetDevicesAvailability(ctx context.Context) (*upcloud.DevicesAvailability, error) {
1525+
args := m.Called()
1526+
if args[0] == nil {
1527+
return nil, args.Error(1)
1528+
}
1529+
return args[0].(*upcloud.DevicesAvailability), args.Error(1)
1530+
}

0 commit comments

Comments
 (0)